Container Extension Points
1.8. Container Extension Points
Typically, an application developer does not need to subclass ApplicationContext
implementation classes. Instead, the Spring IoC container can be extended by plugging in implementations of special integration interfaces. The next few sections describe these integration interfaces.
1.8.1. Customizing Beans by Using a `BeanPostProcessor`
The BeanPostProcessor
interface defines callback methods that you can implement to provide your own (or override the container’s default) instantiation logic, dependency resolution logic, and so forth. If you want to implement some custom logic after the Spring container finishes instantiating, configuring, and initializing a bean, you can plug in one or more custom BeanPostProcessor
implementations.
You can configure multiple BeanPostProcessor
instances, and you can control the order in which these BeanPostProcessor
instances run by setting the order
property. You can set this property only if the BeanPostProcessor
implements the Ordered
interface. If you write your own BeanPostProcessor
, you should consider implementing the Ordered
interface, too. For further details, see the javadoc of the BeanPostProcessor
and Ordered
interfaces. See also the note on programmatic registration of BeanPostProcessor
instances.
BeanPostProcessor
instances operate on bean (or object) instances. That is, the Spring IoC container instantiates a bean instance and thenBeanPostProcessor
instances do their work.
BeanPostProcessor
instances are scoped per-container. This is relevant only if you use container hierarchies. If you define aBeanPostProcessor
in one container, it post-processes only the beans in that container. In other words, beans that are defined in one container are not post-processed by aBeanPostProcessor
defined in another container, even if both containers are part of the same hierarchy.To change the actual bean definition (that is, the blueprint that defines the bean), you instead need to use a
BeanFactoryPostProcessor
, as described in Customizing Configuration Metadata with aBeanFactoryPostProcessor
.
BeanPostProcessor实例的作用域是针对每个容器的。这只有在使用容器层次结构时才有用。如果在一个容器中定义BeanPostProcessor,它将只对该容器中的bean进行后处理。换句话说,在一个容器中定义的bean不会由在另一个容器中定义的BeanPostProcessor进行后处理,即使两个容器都属于相同的层次结构。
要更改实际的bean定义(即定义bean的蓝图),您需要使用BeanFactoryPostProcessor,如使用BeanFactoryPostProcessor自定义配置元数据中所述。
The org.springframework.beans.factory.config.BeanPostProcessor
interface consists of exactly two callback methods. When such a class is registered as a post-processor with the container, for each bean instance that is created by the container, the post-processor gets a callback from the container both before container initialization methods (such as InitializingBean.afterPropertiesSet()
or any declared init
method) are called, and after any bean initialization callbacks. The post-processor can take any action with the bean instance, including ignoring the callback completely. A bean post-processor typically checks for callback interfaces, or it may wrap a bean with a proxy. Some Spring AOP infrastructure classes are implemented as bean post-processors in order to provide proxy-wrapping logic.
An ApplicationContext
automatically detects any beans that are defined in the configuration metadata that implement the BeanPostProcessor
interface. The ApplicationContext
registers these beans as post-processors so that they can be called later, upon bean creation. Bean post-processors can be deployed in the container in the same fashion as any other beans.
Note that, when declaring a BeanPostProcessor
by using an @Bean
factory method on a configuration class, the return type of the factory method should be the implementation class itself or at least the org.springframework.beans.factory.config.BeanPostProcessor
interface, clearly indicating the post-processor nature of that bean. Otherwise, the ApplicationContext
cannot autodetect it by type before fully creating it. Since a BeanPostProcessor
needs to be instantiated early in order to apply to the initialization of other beans in the context, this early type detection is critical.
Programmatically registering
BeanPostProcessor
instancesWhile the recommended approach for
BeanPostProcessor
registration is throughApplicationContext
auto-detection (as described earlier), you can register them programmatically against aConfigurableBeanFactory
by using theaddBeanPostProcessor
method. This can be useful when you need to evaluate conditional logic before registration or even for copying bean post processors across contexts in a hierarchy. Note, however, thatBeanPostProcessor
instances added programmatically do not respect theOrdered
interface. Here, it is the order of registration that dictates the order of execution. Note also thatBeanPostProcessor
instances registered programmatically are always processed before those registered through auto-detection, regardless of any explicit ordering.
虽然推荐的BeanPostProcessor注册方法是通过ApplicationContext自动检测(如前所述),但是您可以通过使用addBeanPostProcessor方法以编程的方式针对ConfigurableBeanFactory注册它们。当您需要在注册前评估条件逻辑,甚至在层次结构中跨上下文复制bean post处理器时,这可能很有用。但是请注意,以编程方式添加的BeanPostProcessor实例不尊重Ordered接口。在这里,登记的顺序决定了执行的顺序。还要注意,无论如何,以编程方式注册的BeanPostProcessor实例总是在通过自动检测注册的实例之前处理任何显式的命令。
BeanPostProcessor
instances and AOP auto-proxyingClasses that implement the
BeanPostProcessor
interface are special and are treated differently by the container. AllBeanPostProcessor
instances and beans that they directly reference are instantiated on startup, as part of the special startup phase of theApplicationContext
. Next, allBeanPostProcessor
instances are registered in a sorted fashion and applied to all further beans in the container. Because AOP auto-proxying is implemented as aBeanPostProcessor
itself, neitherBeanPostProcessor
instances nor the beans they directly reference are eligible for auto-proxying and, thus, do not have aspects woven into them.For any such bean, you should see an informational log message:Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying)
.If you have beans wired into yourBeanPostProcessor
by using autowiring or@Resource
(which may fall back to autowiring), Spring might access unexpected beans when searching for type-matching dependency candidates and, therefore, make them ineligible for auto-proxying or other kinds of bean post-processing. For example, if you have a dependency annotated with@Resource
where the field or setter name does not directly correspond to the declared name of a bean and no name attribute is used, Spring accesses other beans for matching them by type.
实现BeanPostProcessor接口的类是特殊的,容器对它们进行不同的处理。所有BeanPostProcessor实例和它们直接引用的bean都是在启动时实例化的,这是ApplicationContext特殊启动阶段的一部分。接下来,所有BeanPostProcessor实例以一种排序的方式注册,并应用于容器中的所有其他bean。因为AOP自动代理是作为BeanPostProcessor本身实现的,所以无论是BeanPostProcessor实例还是它们直接引用的bean都不符合自动代理的条件,因此,它们没有嵌入方面。对于任何这样的bean,您应该看到一条信息日志消息:bean someBean不适合由所有BeanPostProcessor接口处理(例如:不适合自动代理)。如果您使用自动装配或@Resource(可能会退回到自动装配)将bean连接到BeanPostProcessor中。Spring在搜索类型匹配的依赖候选项时可能会访问意想不到的bean,因此使它们不适合自动代理或其他类型的bean后处理。例如,如果您有一个带@Resource注释的依赖项,其中字段或setter名称不直接对应于bean声明的名称,并且没有使用name属性,那么Spring将访问其他bean以按类型匹配它们。
The following examples show how to write, register, and use BeanPostProcessor
instances in an ApplicationContext
.
Example: Hello World, BeanPostProcessor
-style
This first example illustrates basic usage. The example shows a custom BeanPostProcessor
implementation that invokes the toString()
method of each bean as it is created by the container and prints the resulting string to the system console.
The following listing shows the custom BeanPostProcessor
implementation class definition:
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;
}
}
The following beans
element uses the 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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/lang
https://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>
Notice how the InstantiationTracingBeanPostProcessor
is merely defined. It does not even have a name, and, because it is a bean, it can be dependency-injected as you would any other bean. (The preceding configuration also defines a bean that is backed by a Groovy script. The Spring dynamic language support is detailed in the chapter entitled Dynamic Language Support.)
The following Java application runs the preceding code and configuration:
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 = ctx.getBean("messenger", Messenger.class);
System.out.println(messenger);
}
}
The output of the preceding application resembles the following:
Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961
Example: The AutowiredAnnotationBeanPostProcessor
Using callback interfaces or annotations in conjunction with a custom BeanPostProcessor
implementation is a common means of extending the Spring IoC container. An example is Spring’s AutowiredAnnotationBeanPostProcessor
— a BeanPostProcessor
implementation that ships with the Spring distribution and autowires annotated fields, setter methods, and arbitrary config methods.
1.8.2. Customizing Configuration Metadata with a `BeanFactoryPostProcessor`
The next extension point that we look at is the org.springframework.beans.factory.config.BeanFactoryPostProcessor
. The semantics of this interface are similar to those of the BeanPostProcessor
, with one major difference: BeanFactoryPostProcessor
operates on the bean configuration metadata. That is, the Spring IoC container lets a BeanFactoryPostProcessor
read the configuration metadata and potentially change it before the container instantiates any beans other than BeanFactoryPostProcessor
instances.
You can configure multiple BeanFactoryPostProcessor
instances, and you can control the order in which these BeanFactoryPostProcessor
instances run by setting the order
property. However, you can only set this property if the BeanFactoryPostProcessor
implements the Ordered
interface. If you write your own BeanFactoryPostProcessor
, you should consider implementing the Ordered
interface, too. See the javadoc of the BeanFactoryPostProcessor
and Ordered
interfaces for more details.
If you want to change the actual bean instances (that is, the objects that are created from the configuration metadata), then you instead need to use a
BeanPostProcessor
(described earlier in Customizing Beans by Using aBeanPostProcessor
). While it is technically possible to work with bean instances within aBeanFactoryPostProcessor
(for example, by usingBeanFactory.getBean()
), doing so causes premature bean instantiation, violating the standard container lifecycle. This may cause negative side effects, such as bypassing bean post processing.Also,
BeanFactoryPostProcessor
instances are scoped per-container. This is only relevant if you use container hierarchies. If you define aBeanFactoryPostProcessor
in one container, it is applied only to the bean definitions in that container. Bean definitions in one container are not post-processed byBeanFactoryPostProcessor
instances in another container, even if both containers are part of the same hierarchy.
而且,BeanFactoryPostProcessor实例的作用域是针对每个容器的。这只与使用容器层次结构有关。如果您在一个容器中定义了BeanFactoryPostProcessor,那么它只应用于该容器中的bean定义。一个容器中的Bean定义不会由另一个容器中的BeanFactoryPostProcessor实例进行后处理,即使两个容器都是相同层次结构的一部分。
A bean factory post-processor is automatically run when it is declared inside an ApplicationContext
, in order to apply changes to the configuration metadata that define the container. Spring includes a number of predefined bean factory post-processors, such as PropertyOverrideConfigurer
and PropertySourcesPlaceholderConfigurer
. You can also use a custom BeanFactoryPostProcessor
— for example, to register custom property editors.
An ApplicationContext
automatically detects any beans that are deployed into it that implement the BeanFactoryPostProcessor
interface. It uses these beans as bean factory post-processors, at the appropriate time. You can deploy these post-processor beans as you would any other bean.
As with
BeanPostProcessor
s , you typically do not want to configureBeanFactoryPostProcessor
s for lazy initialization. If no other bean references aBean(Factory)PostProcessor
, that post-processor will not get instantiated at all. Thus, marking it for lazy initialization will be ignored, and theBean(Factory)PostProcessor
will be instantiated eagerly even if you set thedefault-lazy-init
attribute totrue
on the declaration of your<beans />
element.
<beans />
元素的声明中将默认的-lazy-init属性设置为true, Bean(Factory)PostProcessor也会立即实例化。
Example: The Class Name Substitution PropertySourcesPlaceholderConfigurer
You can use the PropertySourcesPlaceholderConfigurer
to externalize property values from a bean definition in a separate file by using the standard Java Properties
format. Doing so enables the person deploying an application to customize environment-specific properties, such as database URLs and passwords, without the complexity or risk of modifying the main XML definition file or files for the container.
Consider the following XML-based configuration metadata fragment, where a DataSource
with placeholder values is defined:
<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>
The example shows properties configured from an external Properties
file. At runtime, a PropertySourcesPlaceholderConfigurer
is applied to the metadata that replaces some properties of the DataSource. The values to replace are specified as placeholders of the form ${property-name}
, which follows the Ant and log4j and JSP EL style.
The actual values come from another file in the standard Java Properties
format:
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root
Therefore, the ${jdbc.username}
string is replaced at runtime with the value, 'sa', and the same applies for other placeholder values that match keys in the properties file. The PropertySourcesPlaceholderConfigurer
checks for placeholders in most properties and attributes of a bean definition. Furthermore, you can customize the placeholder prefix and suffix.
${jdbc.username}
字符串在运行时被替换为值'sa',同样的情况也适用于属性文件中匹配键的其他占位符值。PropertySourcesPlaceholderConfigurer检查bean定义的大多数属性和属性中的占位符。此外,还可以自定义占位符前缀和后缀。
With the context
namespace introduced in Spring 2.5, you can configure property placeholders with a dedicated configuration element. You can provide one or more locations as a comma-separated list in the location
attribute, as the following example shows:
<context:property-placeholder location="classpath:com/something/jdbc.properties"/>
The PropertySourcesPlaceholderConfigurer
not only looks for properties in the Properties
file you specify. By default, if it cannot find a property in the specified properties files, it checks against Spring Environment
properties and regular Java System
properties.
You can use the
PropertySourcesPlaceholderConfigurer
to substitute class names, which is sometimes useful when you have to pick a particular implementation class at runtime. The following example shows how to do so:<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}"/>
If the class cannot be resolved at runtime to a valid class, resolution of the bean fails when it is about to be created, which is during the
preInstantiateSingletons()
phase of anApplicationContext
for a non-lazy-init bean.
如果类不能在运行时解析为有效的类,则在即将创建bean时解析失败,这是在非惰性初始化bean的ApplicationContext的preInstantiateSingletons()阶段。
Example: The PropertyOverrideConfigurer
The PropertyOverrideConfigurer
, another bean factory post-processor, resembles the PropertySourcesPlaceholderConfigurer
, but unlike the latter, the original definitions can have default values or no values at all for bean properties. If an overriding Properties
file does not have an entry for a certain bean property, the default context definition is used.
Note that the bean definition is not aware of being overridden, so it is not immediately obvious from the XML definition file that the override configurer is being used. In case of multiple PropertyOverrideConfigurer
instances that define different values for the same bean property, the last one wins, due to the overriding mechanism.
Properties file configuration lines take the following format:
beanName.property=value
The following listing shows an example of the format:
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb
This example file can be used with a container definition that contains a bean called dataSource
that has driver
and url
properties.
Compound property names are also supported, as long as every component of the path except the final property being overridden is already non-null (presumably initialized by the constructors). In the following example, the sammy
property of the bob
property of the fred
property of the tom
bean is set to the scalar value 123
:
tom.fred.bob.sammy=123
Specified override values are always literal values. They are not translated into bean references. This convention also applies when the original value in the XML bean definition specifies a bean reference.
With the context
namespace introduced in Spring 2.5, it is possible to configure property overriding with a dedicated configuration element, as the following example shows:
<context:property-override location="classpath:override.properties"/>
1.8.3. Customizing Instantiation Logic with a `FactoryBean`
You can implement the org.springframework.beans.factory.FactoryBean
interface for objects that are themselves factories.
The FactoryBean
interface is a point of pluggability into the Spring IoC container’s instantiation logic. If you have complex initialization code that is better expressed in Java as opposed to a (potentially) verbose amount of XML, you can create your own FactoryBean
, write the complex initialization inside that class, and then plug your custom FactoryBean
into the container.
The FactoryBean<T>
interface provides three methods:
T getObject()
: Returns an instance of the object this factory creates. The instance can possibly be shared, depending on whether this factory returns singletons or prototypes.boolean isSingleton()
: Returnstrue
if thisFactoryBean
returns singletons orfalse
otherwise. The default implementation of this method returnstrue
.Class<?> getObjectType()
: Returns the object type returned by thegetObject()
method ornull
if the type is not known in advance.
- T getObject():返回该工厂创建的对象的实例。这个实例可能是共享的,这取决于这个工厂返回的是单例还是原型。
- boolean isSingleton():如果该FactoryBean返回单例,则返回true,否则返回false。这个方法的默认实现返回true。
- Class getObjectType():返回getObject()方法返回的对象类型,如果事先不知道类型,则返回null。
The FactoryBean
concept and interface are used in a number of places within the Spring Framework. More than 50 implementations of the FactoryBean
interface ship with Spring itself.
When you need to ask a container for an actual FactoryBean
instance itself instead of the bean it produces, prefix the bean’s id
with the ampersand symbol (&
) when calling the getBean()
method of the ApplicationContext
. So, for a given FactoryBean
with an id
of myBean
, invoking getBean("myBean")
on the container returns the product of the FactoryBean
, whereas invoking getBean("&myBean")
returns the FactoryBean
instance itself.