BeanPostProcessor和BeanFactoryPostProcessor的区别

官方文档:

在Spring核心的1.8章节

使用BeanPostProcessor自定义Bean

BeanPostProcessor 接口定义了您可以实现的回调方法,以提供您自己的(或覆盖容器的默认)实例化逻辑,依赖关系解析逻辑等。如果要在Spring容器完成实例化,配置和初始化bean之后实现某些自定义逻辑,则可以插入一个或多个 BeanPostProcessor 实现。

您可以配置多个 BeanPostProcessor 实例,并且可以通过设置 order 属性来控制这些 BeanPostProcessor 实例的执行顺序。你可以设置仅当BeanPostProcessor 实现 Ordered 接口时才具有此属性。如果你自己编写 BeanPostProcessor ,你也应该考虑实现 Ordered 接口。有关更多详细信息,请参阅 BeanPostProcessorOrdered 接口的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 接口。有关更多详细信息,请参阅 BeanFactoryPostProcessorOrdered 接口的javadoc。

如果要更改实际的bean实例(即从配置元数据创建的对象),则需要使用 BeanPostProcessor 。虽然技术上可以在 BeanFactoryPostProcessor 中使用bean实例(例如,通过使用 BeanFactory.getBean() ),但这样做会导致过早的bean实例化,从而违反标准容器生命周期。这可能会导致负面影响,例如绕过bean后期处理。

此外, BeanFactoryPostProcessor 实例的范围是每个容器的范围。仅当您使用容器层次结构时,这才有意义。如果在一个容器中定义BeanFactoryPostProcessor ,则它仅应用于该容器中的bean定义。即使两个容器都是同一层次结构的一部分,一个容器中的Bean定义也不会被另一个容器中的BeanFactoryPostProcessor 实例进行后处理。

Bean工厂后处理器在 ApplicationContext 中声明时会自动执行,以便将更改应用于定义容器的配置元数据。 Spring包含许多预定义的bean工厂后处理器,例如 PropertyOverrideConfigurerPropertyPlaceholderConfigurer 。您还可以使用自定义 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 且具有 driverurl 属性的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);
        
    
    }

}

 

猜你喜欢

转载自www.cnblogs.com/dalianpai/p/12018232.html