官方文档:
在Spring核心的1.8章节
使用BeanPostProcessor自定义Bean
BeanPostProcessor 接口定义了您可以实现的回调方法,以提供您自己的(或覆盖容器的默认)实例化逻辑,依赖关系解析逻辑等。如果要在Spring容器完成实例化,配置和初始化bean之后实现某些自定义逻辑,则可以插入一个或多个 BeanPostProcessor 实现。
您可以配置多个 BeanPostProcessor 实例,并且可以通过设置 order 属性来控制这些 BeanPostProcessor 实例的执行顺序。你可以设置仅当BeanPostProcessor 实现 Ordered 接口时才具有此属性。如果你自己编写 BeanPostProcessor ,你也应该考虑实现 Ordered 接口。有关更多详细信息,请参阅 BeanPostProcessor 和 Ordered 接口的javadoc。
BeanPostProcessor实例在bean(或对象)实例上运行。也就是说,Spring IoC容器实例化一个bean实例,然后BeanPostProcessor实例完成它们的工作。
BeanPostProcessor 实例的范围是每个容器的范围。仅当您使用容器层次结构时,这才是相关的。如果在一个容器中定义 BeanPostProcessor ,则它仅对该容器中的bean进行后处理。换句话说,在一个容器中定义的bean不会被另一个容器中定义的 BeanPostProcessor 进行后处理,即使两个容器都是同一层次结构的一部分。
要更改实际的bean定义(即定义bean的蓝图),您需要使用 BeanFactoryPostProcessor
org.springframework.beans.factory.config.BeanPostProcessor 接口恰好包含两个回调方法。当这样的类被注册为带有容器的后处理器时,对于容器创建的每个bean实例,后处理器在容器初始化方法之前从容器中获取回调(例如 InitializingBean.afterPropertiesSet() ,在任何声明的 init 之后)方法)被调用,并在任何bean初始化后回调。后处理器可以对bean实例执行任何操作,包括完全忽略回调。 bean后处理器通常检查回调接口,或者它可以用代理包装bean。一些Spring AOP基础结构类实现为bean后处理器,以便提供代理包装逻辑。
ApplicationContext 自动检测在实现 BeanPostProcessor 接口的配置元数据中定义的任何bean。 ApplicationContext 将这些bean注册为后处理器,以便稍后在创建bean时调用它们。 Bean后处理器可以以与任何其他bean相同的方式部署在容器中。
请注意,在配置类上使用 @Bean factory方法声明 BeanPostProcessor 时,工厂方法的返回类型应该是实现类本身或至少是org.springframework.beans.factory.config.BeanPostProcessor 接口,清楚地表明该bean的后处理器性质。否则, ApplicationContext 无法在完全创建之前按类型自动检测它。由于需要提前实例化 BeanPostProcessor 以便应用于上下文中其他bean的初始化,因此这种早期类型检测至关重要。
以编程方式注册
BeanPostProcessor实例 虽然BeanPostProcessor注册的推荐方法是通过ApplicationContext自动检测(如前所述),但您可以使用addBeanPostProcessor方法以编程方式对ConfigurableBeanFactory注册它们。当您需要在注册前评估条件逻辑或甚至跨层次结构中的上下文复制Bean post处理器时,这非常有用。但请注意,以编程方式添加的BeanPostProcessor实例不尊重Ordered接口。这里,注册的顺序决定了执行的顺序。另请注意,以编程方式注册的BeanPostProcessor实例始终在通过自动检测注册的实例之前处理,而不管任何显式排序。
BeanPostProcessor实例和AOP自动代理
实现 BeanPostProcessor 接口的类是特殊的,容器会对它们进行不同的处理。作为 ApplicationContext 特殊启动阶段的一部分,它们直接引用的所有BeanPostProcessor 实例和bean都会在启动时实例化。接下来,所有 BeanPostProcessor 实例都以排序方式注册,并应用于容器中的所有其他bean。因为AOP自动代理是作为 BeanPostProcessor 本身实现的,所以 BeanPostProcessor 实例和它们直接引用的bean都不符合自动代理的条件,因此没有将方面编入其中。
对于任何此类bean,您应该看到一条信息性日志消息: Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying) 。
如果您通过使用自动装配或 @Resource (可能会回退到自动装配)将bean连接到 BeanPostProcessor ,则Spring可能会在搜索类型匹配依赖项候选项时访问意外的bean,从而使它们不符合自动代理或其他类型的 beans 后处理。例如,如果您有一个使用 @Resource 注释的依赖项,其中字段或setter名称不直接对应于bean的声明名称且未使用name属性,则Spring会访问其他bean以按类型匹配它们。
以下示例显示如何在 ApplicationContext 中编写,注册和使用 BeanPostProcessor 实例。
示例:Hello World,BeanPostProcessor样式
第一个例子说明了基本用法。该示例显示了一个自定义 BeanPostProcessor 实现,它调用容器创建的每个bean的 toString() 方法,并将生成的字符串打印到系统控制台。
以下清单显示了自定义 BeanPostProcessor 实现类定义:
package scripting; import org.springframework.beans.factory.config.BeanPostProcessor; public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor { // simply return the instantiated bean as-is public Object postProcessBeforeInitialization(Object bean, String beanName) { return bean; // we could potentially return any object reference here... } public Object postProcessAfterInitialization(Object bean, String beanName) { System.out.println("Bean '" + beanName + "' created : " + bean.toString()); return bean; } }
以下 beans 元素使用 InstantiationTracingBeanPostProcessor :
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:lang="http://www.springframework.org/schema/lang" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang.xsd"> <lang:groovy id="messenger" script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy"> <lang:property name="message" value="Fiona Apple Is Just So Dreamy."/> </lang:groovy> <!-- when the above bean (messenger) is instantiated, this custom BeanPostProcessor implementation will output the fact to the system console --> <bean class="scripting.InstantiationTracingBeanPostProcessor"/> </beans>
注意 InstantiationTracingBeanPostProcessor 仅仅是如何定义的。它甚至没有名称,并且,因为它是一个bean,它可以像任何其他bean一样依赖注入。 (前面的配置还定义了一个由Groovy脚本支持的bean。Spring动态语言支持在 Headers 为 Dynamic Language Support 的章节中有详细说明。)
以下Java应用程序运行上述代码和配置:
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.scripting.Messenger; public final class Boot { public static void main(final String[] args) throws Exception { ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml"); Messenger messenger = (Messenger) ctx.getBean("messenger"); System.out.println(messenger); } }
上述应用程序的输出类似于以下内容:
Bean 'messenger' created : [email protected] [email protected]
示例:RequiredAnnotationBeanPostProcessor
将回调接口或注释与自定义 BeanPostProcessor 实现结合使用是扩展Spring IoC容器的常用方法。一个例子是Spring的RequiredAnnotationBeanPostProcessor - 一个带有Spring发行版的 BeanPostProcessor 实现,它确保用(任意)注释标记的bean上的JavaBean属性实际上(配置为)依赖注入值。
使用BeanFactoryPostProcessor自定义配置元数据
我们看到的下一个扩展点是 org.springframework.beans.factory.config.BeanFactoryPostProcessor 。此接口的语义类似于 BeanPostProcessor 的语义,但有一个主要区别: BeanFactoryPostProcessor 对bean配置元数据进行操作。也就是说,Spring IoC容器允许 BeanFactoryPostProcessor 读取配置元数据,并可能在容器实例化除 BeanFactoryPostProcessor 实例之外的任何bean之前更改它。
您可以配置多个 BeanFactoryPostProcessor 实例,并且可以通过设置 order 属性来控制这些 BeanFactoryPostProcessor 实例的运行顺序。但是,如果BeanFactoryPostProcessor 实现 Ordered 接口,则只能设置此属性。如果你自己编写 BeanFactoryPostProcessor ,你也应该考虑实现 Ordered 接口。有关更多详细信息,请参阅 BeanFactoryPostProcessor 和 Ordered 接口的javadoc。
如果要更改实际的bean实例(即从配置元数据创建的对象),则需要使用
BeanPostProcessor。虽然技术上可以在BeanFactoryPostProcessor中使用bean实例(例如,通过使用BeanFactory.getBean()),但这样做会导致过早的bean实例化,从而违反标准容器生命周期。这可能会导致负面影响,例如绕过bean后期处理。
此外, BeanFactoryPostProcessor 实例的范围是每个容器的范围。仅当您使用容器层次结构时,这才有意义。如果在一个容器中定义BeanFactoryPostProcessor ,则它仅应用于该容器中的bean定义。即使两个容器都是同一层次结构的一部分,一个容器中的Bean定义也不会被另一个容器中的BeanFactoryPostProcessor 实例进行后处理。
Bean工厂后处理器在 ApplicationContext 中声明时会自动执行,以便将更改应用于定义容器的配置元数据。 Spring包含许多预定义的bean工厂后处理器,例如 PropertyOverrideConfigurer 和 PropertyPlaceholderConfigurer 。您还可以使用自定义 BeanFactoryPostProcessor - 例如,注册自定义属性编辑器。
ApplicationContext 会自动检测部署到其中的任何实现 BeanFactoryPostProcessor 接口的bean。它在适当的时候使用这些bean作为bean工厂后处理器。您可以像处理任何其他bean一样部署这些后处理器bean。
与
BeanPostProcessor一样,您通常不希望为延迟初始化配置BeanFactoryPostProcessor。如果没有其他bean引用Bean(Factory)PostProcessor,则该后处理器根本不会被实例化。因此,将忽略将其标记为延迟初始化,即使您在<beans />元素的声明上将default-lazy-init属性设置为true,也会急切地实例化Bean(Factory)PostProcessor。
示例:类名替换PropertyPlaceholderConfigurer
您可以使用标准Java Properties 格式在单独的文件中使用 PropertyPlaceholderConfigurer 来外部化bean定义中的属性值。这样做可以使部署应用程序的人员自定义特定于环境的属性,例如数据库URL和密码,而不会出现修改主XML定义文件或容器文件的复杂性或风险。
请考虑以下基于XML的配置元数据片段,其中定义了带有占位符值的 DataSource :
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations" value="classpath:com/something/jdbc.properties"/> </bean> <bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean>
该示例显示了从外部 Properties 文件配置的属性。在运行时, PropertyPlaceholderConfigurer 应用于替换DataSource的某些属性的元数据。要替换的值指定为 ${property-name} 形式的占位符,它遵循Ant和log4j以及JSP EL样式。
实际值来自标准Java Properties 格式的另一个文件:
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root
因此, ${jdbc.username} 字符串在运行时被替换为值'sa',并且同样适用于与属性文件中的键匹配的其他占位符值。 PropertyPlaceholderConfigurer 检查bean定义的大多数属性和属性中的占位符。此外,您可以自定义占位符前缀和后缀。
使用Spring 2.5中引入的 context 命名空间,您可以使用专用配置元素配置属性占位符。您可以在 location 属性中以逗号分隔列表的形式提供一个或多个位置,如以下示例所示:
<context:property-placeholder location="classpath:com/something/jdbc.properties"/>
PropertyPlaceholderConfigurer 不仅在您指定的 Properties 文件中查找属性。默认情况下,如果它在指定的属性文件中找不到属性,它还会检查JavaSystem 属性。您可以通过使用以下三个受支持的整数值之一设置configurer的 systemPropertiesMode 属性来自定义此行为:
-
never(0):从不检查系统属性。 -
fallback(1):如果在指定的属性文件中无法解析,则检查系统属性。这是默认值。 -
override(2):在尝试指定的属性文件之前,首先检查系统属性。这使系统属性可以覆盖任何其他属性源。
有关更多信息,请参阅 PropertyPlaceholderConfigurer javadoc。
您可以使用
PropertyPlaceholderConfigurer替换类名,这在您必须在运行时选择特定实现类时有时很有用。以下示例显示了如何执行此操作:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <value>classpath:com/something/strategy.properties</value> </property> <property name="properties"> <value>custom.strategy.class=com.something.DefaultStrategy</value> </property> </bean> <bean id="serviceStrategy" class="${custom.strategy.class}"/>
如果在运行时无法将类解析为有效类,则在将要创建bean时,bean的解析将失败,这在非延迟初始化bean的 preInstantiateSingletons() 阶段期间会失败。
示例:PropertyOverrideConfigurer
PropertyOverrideConfigurer ,另一个bean工厂后处理器,类似于 PropertyPlaceholderConfigurer ,但与后者不同,原始定义可以具有默认值或根本没有值用于bean属性。如果重写的 Properties 文件没有某个bean属性的条目,则使用默认的上下文定义。
请注意,bean定义不知道被覆盖,因此从XML定义文件中可以立即看出正在使用覆盖配置器。如果多个 PropertyOverrideConfigurer 实例为同一个bean属性定义了不同的值,则由于覆盖机制,最后一个获胜。
属性文件配置行采用以下格式:
beanName.property=value
以下清单显示了格式的示例:
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb
此示例文件可以与包含名为 dataSource 且具有 driver 和 url 属性的bean的容器定义一起使用。
也支持复合属性名称,只要路径的每个组件(重写的最终属性除外)都已经非空(可能由构造函数初始化)。在以下示例中, tom bean的 fred 属性的 bob属性的 sammy 属性设置为标量值 123 :
tom.fred.bob.sammy=123
使用Spring 2.5中引入的 context 命名空间,可以使用专用配置元素配置属性覆盖,如以下示例所示:
<context:property-override location="classpath:override.properties"/>
代码:
@Component public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { System.out.println("MyBeanFactoryPostProcessor...postProcessBeanFactory..."); int count = beanFactory.getBeanDefinitionCount(); String[] names = beanFactory.getBeanDefinitionNames(); System.out.println("当前BeanFactory中有"+count+" 个Bean"); System.out.println(Arrays.asList(names)); } }
@Component public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { System.out.println("MyBeanFactoryPostProcessor...postProcessBeanFactory..."); int count = beanFactory.getBeanDefinitionCount(); String[] names = beanFactory.getBeanDefinitionNames(); System.out.println("当前BeanFactory中有"+count+" 个Bean"); System.out.println(Arrays.asList(names)); } }
测试结果:

@Component public class MyBeanPostProcessor implements BeanPostProcessor,Ordered { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { // TODO Auto-generated method stub System.out.println("postProcessBeforeInitialization..."+beanName+"=>"+bean); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { // TODO Auto-generated method stub System.out.println("postProcessAfterInitialization..."+beanName+"=>"+bean); return bean; } @Override public int getOrder() { return 2; } }
@Configuration @ComponentScan(value="com.atguigu.bean") public class MainConfig3 { //给容器中注册一个Bean;类型为返回值的类型,id默认是用方法名作为id @Bean("person") public Person person01(){ return new Person("lisi", 20); } }
测试:
public class MainTest { @SuppressWarnings("resource") public static void main(String[] args) { // ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml"); // Person bean = (Person) applicationContext.getBean("person"); // System.out.println(bean); ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig3.class); Person bean = applicationContext.getBean(Person.class); System.out.println(bean); } }
