Spring IoC部分被设计成可扩展的。开发者通常不需要继承各种各样的 BeanFactory
或者 ApplicationContext
的实现类。通过插特殊集成接口的实现,可以无限扩展Spring IoC容器。 说白了,扩展点
就是允许你在不修改 Spring 源码的情况下,通过实现一些 Spring 预留的接口来把你自己的代码融入到Spring IoC容器初始化的过程中。接下来将详细介绍所有这些不同的集成接口。
BeanPostProcessor
定制BeanBeanPostProcessor
接口定义了可以实现的回调方法,以提供定制化的(或覆盖容器的默认)实例化逻辑、依赖关系解析逻辑等。如果想在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方法和任意配置方法。
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工厂后处理器,例如PropertyOverrideConfigurer
和PropertySourcesPlaceholderConfigurer
。你还可以使用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>
该示例显示从外部文件配置的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 Environment
和System
属性进行检查。
可以使用
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
以下清单显示了该格式的示例:
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb
这个示例文件可以与容器定义一起使用,容器定义包含一个名为dataSource
的Bean,该Bean具有driver
和url
属性。
也支持复合属性名,只要路径的每个组件(被重写的最终属性除外)都已非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"/>
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 getObject()
:返回此工厂创建的对象的实例。实例可以共享,具体取决于此工厂返回的是单例还是原型boolean isSingleton()
:如果FactoryBean
返回单例,则返回true
,否则返回false
。此方法的默认实现返回 true
Class> getObjectType()
:返回getObject()
方法返回的对象类型,或者如果事先不知道该类型,则返回null
FactoryBean
概念和接口在Spring框架中的许多地方使用。超过50个FactoryBean
接口的实现与Spring本身一起提供。
当需要向容器请求实际的FactoryBean
实例本身而不是它生成的 Bean 时,请在调用ApplicationContext
的getBean()
方法时,在 Bean 的id
前面加上&
符号作为前缀。因此,对于id
为myBean
的给定FactoryBean
,在容器上调用getBean("myBean")
将返回FactoryBean
的产品,而调用getBean("&myBean")
将返回FactoryBean
实例本身。