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;
	}
}

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

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

注意!!!只有低级的容器,一般是名字中带 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;
    }
}

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

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

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。因此,将其标记为延迟初始化将被忽略,并且即使您在<beans/>元素的声明上将属性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>

该示例显示从外部文件配置的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

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

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

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

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

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

<bean class="org.springframework.beans.factory.config.PropertySourcesPlaceholderConfigurer">
    <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}"/>

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

示例:PropertyOverrideConfigurer

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

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

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

beanName.property=value

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

dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb

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

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

tom.fred.bob.sammy=123

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

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

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

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;
	}
}

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

FactoryBean<T>接口提供三种方法:

  • 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实例本身。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

纯纯的小白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值