• 8、IOC 之容器扩展点


    8、IOC 之容器扩展点

    Spring IoC部分被设计成可扩展的。开发者通常不需要继承各种各样的 BeanFactory 或者 ApplicationContext 的实现类。通过插特殊集成接口的实现,可以无限扩展Spring IoC容器。 说白了,扩展点 就是允许你在不修改 Spring 源码的情况下,通过实现一些 Spring 预留的接口来把你自己的代码融入到Spring IoC容器初始化的过程中。接下来将详细介绍所有这些不同的集成接口。

    8.1、通过 BeanPostProcessor 定制Bean

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

    你可以配置多个 BeanPostProcessor 实例,并且可以通过设置 order 属性来控制这些 BeanPostProcessor 的执行顺序。只有当 BeanPostProcessor 实现 Ordered 接口时,才能设置此属性;如果你编写自己的 BeanPostProcessor,你也应该考虑实现 Ordered 接口。

    BeanPostProcessor实例在 Bean(或对象)实例上进行操作。也就是说,Spring IoC 容器实例化一个 Bean 实例,然后 BeanPostProcessor实例执行其工作。

    BeanPostProcessor实例的作用域为每个容器。仅当使用容器层次结构时才相关。如果在一个容器中定义 BeanPostProcessor,则它仅对该容器中的 Bean 进行后置处理。换句话说,在一个容器中定义的 Bean 不会由另一个容器中定义的 BeanPostProcessor进行后处理,即使两个容器都是同一层次结构的一部分。

    要更改实际的 Bean 定义(即定义 Bean 的蓝图),需要使用 BeanFactoryPostProcessor,如使用 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工厂方法声明BeanPostProcessor时,工厂方法的返回类型应该是实现类本身,或者至少是org.springframework.beans.factory.config.BeanPostProcessor接口,这清楚地指示了该 Bean 的后处理器性质。否则,ApplicationContext在完全创建之前无法按类型自动检测它。由于BeanPostProcessor需要尽早实例化才能应用于上下文中其他 bean 的初始化,因此这种早期类型检测至关重要。

    以编程方式注册BeanPostProcessor实例

    虽然BeanPostProcessor推荐的注册方法是通过ApplicationContext自动检测(如上所述),但可以使用addBeanPostProcessor方法对ConfigurableBeanFactory以编程方式针注册它们。当需要在注册之前评估条件逻辑时,甚至对于跨层次结构中的上下文复制 Bean 后处理器时,这可能很有用。但请注意,以编程方式添加的BeanPostProcessor实例不遵循Ordered接口。在这里,注册的顺序决定了执行的顺序。另请注意,以编程方式注册的BeanPostProcessor实例始终先于通过自动检测注册的实例进行处理,而不管任何显式排序如何。

    BeanPostProcessor实例和AOP自动代理

    实现BeanPostProcessor接口的类是特殊的,容器会对它们进行不同的处理。所有BeanPostProcessor和它们直接引用的 Bean 都会在启动时实例化,作为ApplicationContext的特殊启动阶段的一部分。接下来,以排序方式注册所有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,从而使它们不适合自动代理或其他类型的 Bean 后处理的条件。例如,如果你有一个使用@Resource注释的依赖项,其中字段 / setter 名称不直接对应于 Bean 的声明名称,并且没有使用 name 属性,则 Spring 将访问其他 bean 以按类型匹配它们。

    BeanPostProcessor接口:

    没错!BeanPostProcessor 的两个回调就是bean生命周期中初始化时(实例化后)大名鼎鼎的前置处理器和后置处理器!

    package org.springframework.beans.factory.config;
    
    import org.springframework.beans.BeansException;
    import org.springframework.lang.Nullable;
    
    public interface BeanPostProcessor {
    
    	@Nullable
    	default Object postProcessBeforeInitialization(Object bean, String beanName) 
            throws BeansException {
    		return bean;
    	}
    
    	@Nullable
    	default Object postProcessAfterInitialization(Object bean, String beanName) 
            throws BeansException {
    		return bean;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    ◆ 下示例显示如何在ApplicationContext中写入、注册和使用BeanPostProcessor实例。

    ConfigurableBeanFactory factory = new XmlBeanFactory(...);
                
    // 现在注册任何需要的BeanPostProcessor实例
    MyBeanPostProcessor postProcessor = new MyBeanPostProcessor();
    factory.addBeanPostProcessor(postProcessor);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    注意!!!只有低级的容器,一般是名字中带 BeanFactory 的容器才需要像上面这样手动注册,高级的容器,如ApplicationContext的实现类会自动帮你完成BeanPostProcessor的注册(可以查看AbstractApplicationContext类的refresh()中的registerBeanPostProcessors(beanFactory)部分) 。

    BeanPostProcessor 示例

    package hom.wang;
    
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.config.BeanPostProcessor;
    
    public class MyBeanPostProcessor implements BeanPostProcessor {
    
        public Object postProcessBeforeInitialization(Object bean, String beanName) 
            throws BeansException {
            System.out.println("前置处理器处理--" + beanName);
            return bean;
        }
    
        public Object postProcessAfterInitialization(Object bean, String beanName) 
            throws BeansException {
            System.out.println("后置处理器处理--" + beanName);
            return bean;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    xml 配置(扔给Spring容器就完了,Spring会自动注册滴!):

    <bean class="hom.wang.MyBeanPostProcessor"/>
    
    • 1

    AutowiredAnnotationBeanPostProcessor

    将回调接口或注释与自定义BeanPostProcessor实现结合使用是扩展Spring IoC容器的常用方法。一个例子是Spring的AutowiredAnnotationBeanPostProcessor - 一个与Spring发行版一起提供的实现,自动连接带注释的字段、setter方法和任意配置方法。

    8.2、使用BeanFactoryPostProcessor自定义配置元数据

    另一个扩展点 org.springframework.beans.factory.config.BeanFactoryPostProcessor。这个接口的语义和BeanPostProcessor相似,但是有一个主要的区别:BeanFactoryPostProcessor是对Bean的配置元数据进行操作,也就是说,spring IoC容器允许读取配置元数据,并在容器实例化Bean之前对Bean定义的配置元数据进行修改。

    可以配置多个BeanFactoryPostProcessor实例,并且可以通过设置order属性来控制这些实例的运行顺序。但是,仅当 BeanFactoryPostProcessor实现Ordered接口时,才能设置此属性。如果你写你自己的BeanFactoryPostProcessor,你也应该考虑实现Ordered接口。

    注意!!!BeanFactoryPostProcessor的作用域也是容器级的,一个容器里的BeanFactoryPostProcessor不能改变另一个容器中的BeanDefinition

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

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

    当 Bean 工厂后处理器在ApplicationContext 内部声明时,它会自动运行,以便将更改应用于定义容器的配置元数据。Spring 包括许多预定义的Bean工厂后处理器,例如PropertyOverrideConfigurerPropertySourcesPlaceholderConfigurer。你还可以使用BeanFactoryPostProcessor 自定义,例如,注册自定义属性编辑器。

    BeanPostProcessor一样,你通常不希望BeanFactoryPostProcessor为延迟初始化配置 。如果没有其他 bean 引用 Bean(Factory)PostProcessor,则该后处理器将根本不实例化Bean(Factory)PostProcessor。因此,将其标记为延迟初始化将被忽略,并且即使您在元素的声明上将属性default-lazy-init设置为 true,也会急切地实例化 。

    示例:类名替换PropertySourcesPlaceholderConfigurer

    你可以使用PropertySourcesPlaceholderConfigurer ,通过使用标准 Java 格式将 Bean 定义中的Properties属性值外部化到单独的文件中。这样做使部署应用程序的人员能够自定义特定于环境的属性(如数据库 URL 和密码),而不会产生修改容器的主 XML 定义文件的复杂性或风险。

    请考虑以下基于 XML 的配置元数据片段,其中定义了带有占位符值的DataSource

    <bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
        <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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    该示例显示从外部文件配置的Properties属性。在运行时,将PropertySourcesPlaceholderConfigurer 应用于替换数据源的某些属性的元数据。要替换的值被指定为${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
    
    • 1
    • 2
    • 3
    • 4

    因此,字符串${jdbc.username}在运行时将替换为值“sa”,这同样适用于与属性文件中的键匹配的其他占位符值。检查 Bean 定义的大多数属性和特性中的占位符。此外,你可以自定义占位符前缀和后缀。

    使用Spring 2.5中引入的命名空间,你可以使用专用的配置元素context配置location属性占位符。您可以在属性中以逗号分隔的列表的形式提供一个或多个位置,如以下示例所示:

    <context:property-placeholder location="classpath:com/something/jdbc.properties"/>
    
    • 1

    PropertySourcesPlaceholderConfigurer不仅会在你指定的文件中查找属性。默认情况下,如果它在指定的属性文件中找不到属性,它将根据 Spring Properties属性和常规 Java EnvironmentSystem属性进行检查。

    可以使用 PropertySourcesPlaceholderConfigurer来替换类名,当你必须在运行时选取特定的实现类时,这有时很有用。下面的示例演示如何执行此操作:

    <bean class="org.springframework.beans.factory.config.PropertySourcesPlaceholderConfigurer">
        <property name="locations">
            <value>classpath:com/something/strategy.propertiesvalue>
        property>
        <property name="properties">
            <value>custom.strategy.class=com.something.DefaultStrategyvalue>
        property>
    bean>
    
    <bean id="serviceStrategy" class="${custom.strategy.class}"/>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    如果无法在运行时将类解析为有效类,则ApplicationContext在将要创建 Bean 时(即在非惰性初始化bean的阶段)将解析该 Bean 失败。

    示例:PropertyOverrideConfigurer

    PropertyOverrideConfigurer是另一个Bean工厂后处理器,类似于PropertySourcesPlaceholderConfigurer配置器,但与后者不同,原始定义可以有默认值,也可以没有Bean属性的值。如果重写Properties文件没有特定bean属性的条目,则使用默认上下文定义。

    请注意,Bean定义不知道被覆盖,因此从XML定义文件中不能立即看出正在使用覆盖配置器。如果多个PropertyOverrideConfigurer实例为同一Bean属性定义了不同的值,由于覆盖机制,最后一个实例获胜。

    属性文件配置行采用以下格式:

    beanName.property=value
    
    • 1

    以下清单显示了该格式的示例:

    dataSource.driverClassName=com.mysql.jdbc.Driver
    dataSource.url=jdbc:mysql:mydb
    
    • 1
    • 2

    这个示例文件可以与容器定义一起使用,容器定义包含一个名为dataSource的Bean,该Bean具有driverurl属性。

    也支持复合属性名,只要路径的每个组件(被重写的最终属性除外)都已非null(可能由构造函数初始化)。在以下示例中,tom bean的fred属性的bob属性的sammy属性设置为标量值123

    tom.fred.bob.sammy=123
    
    • 1

    指定的覆盖值始终是文字值。它们不会被转换为 Bean引用。当XML Bean定义中的原始值指定 Bean引用时,该约定也适用。

    使用 Spring 2.5 中引入的context命名空间,可以使用专用的配置元素配置属性覆盖,如以下示例所示:

    <context:property-override location="classpath:override.properties"/>
    
    • 1

    8.3、使用FactoryBean定制实例化逻辑

    你可以实现org.springframework.beans.factory.FactoryBean接口。

    package org.springframework.beans.factory;
    
    import org.springframework.lang.Nullable;
    
    public interface FactoryBean<T> {
    
    	String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
    
    	@Nullable
    	T getObject() throws Exception;
    
    	@Nullable
    	Class<?> getObjectType();
    
    	default boolean isSingleton() {
    		return true;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    FactoryBean接口是Spring IoC 容器实例化逻辑的可插入点。如果你有用 Java 更好地表示的复杂初始化代码,而不是(可能)冗长的 XML,则可以创建自己的FactoryBean ,在该类中编写复杂的初始化,然后将自定义FactoryBean插入容器中。

    FactoryBean接口提供三种方法:

    • T getObject():返回此工厂创建的对象的实例。实例可以共享,具体取决于此工厂返回的是单例还是原型
    • boolean isSingleton():如果FactoryBean返回单例,则返回true,否则返回false。此方法的默认实现返回 true
    • Class getObjectType():返回getObject()方法返回的对象类型,或者如果事先不知道该类型,则返回null

    FactoryBean概念和接口在Spring框架中的许多地方使用。超过50个FactoryBean接口的实现与Spring本身一起提供。

    当需要向容器请求实际的FactoryBean实例本身而不是它生成的 Bean 时,请在调用ApplicationContextgetBean()方法时,在 Bean 的id前面加上& 符号作为前缀。因此,对于idmyBean的给定FactoryBean,在容器上调用getBean("myBean")将返回FactoryBean的产品,而调用getBean("&myBean")将返回FactoryBean实例本身。

  • 相关阅读:
    Spring Cloud - 手写 Gateway 源码,实现自定义局部 FilterFactory
    数据结构与算法【Java】08---树结构的实际应用
    macOS磁盘分区调整软件--Paragon Camptune X 中文
    [附源码]java毕业设计基于web场馆预约管理系统
    Elasticsearch:无需基本身份验证即可创建用于访问的不记名令牌
    TS 导入导出那些事
    SQL注入作业
    C#操作GridView控件绑定数据实例详解(二)
    关于深度学习和大模型的基础认知
    linux实现开机自启动服务/脚本
  • 原文地址:https://blog.csdn.net/qq_30769437/article/details/126772837