Container Extension Points
Typically, an application developer does not need to subclass ApplicationContext
implementation classes. Instead, the Infra IoC container can be extended by plugging in
implementations of special integration interfaces. The next few sections describe these
integration interfaces.
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
Infra 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.
To change the actual bean definition (that is, the blueprint that defines the bean),
you instead need to use a |
The cn.taketoday.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 Infra 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 cn.taketoday.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
While the recommended approach for BeanPostProcessor instancesBeanPostProcessor registration is through
ApplicationContext auto-detection (as described earlier), you can register them
programmatically against a ConfigurableBeanFactory by using the addBeanPostProcessor
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, that BeanPostProcessor instances added programmatically do not respect
the Ordered interface. Here, it is the order of registration that dictates the order
of execution. Note also that BeanPostProcessor instances registered programmatically
are always processed before those registered through auto-detection, regardless of any
explicit ordering.
|
BeanPostProcessor instances and AOP auto-proxyingClasses that implement the For any such bean, you should see an informational log message: If you have beans wired into your |
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:
-
Java
package scripting;
import cn.taketoday.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:cn/taketoday/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 Infra dynamic language support is detailed in the chapter entitled
Dynamic Language Support.)
The following Java application runs the preceding code and configuration:
-
Java
import cn.taketoday.context.ApplicationContext;
import cn.taketoday.context.support.ClassPathXmlApplicationContext;
import cn.taketoday.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 : cn.taketoday.scripting.groovy.GroovyMessenger@272961 cn.taketoday.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 Infra IoC container. An example is
Infra AutowiredAnnotationBeanPostProcessor
— a BeanPostProcessor
implementation
that ships with the Infra distribution and autowires annotated fields, setter methods,
and arbitrary config methods.
Customizing Configuration Metadata with a BeanFactoryPostProcessor
The next extension point that we look at is the
cn.taketoday.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 Infra 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 Also, |
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. Infra 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 configure
BeanFactoryPostProcessor s for lazy initialization. If no other bean references a
Bean(Factory)PostProcessor , that post-processor will not get instantiated at all.
Thus, marking it for lazy initialization will be ignored, and the
Bean(Factory)PostProcessor will be instantiated eagerly even if you set the
default-lazy-init attribute to true on the declaration of your <beans /> element.
|
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="cn.taketoday.context.support.PropertySourcesPlaceholderConfigurer">
<property name="locations" value="classpath:com/something/jdbc.properties"/>
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<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.
With the context
namespace introduced in Infra 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 Infra Environment
properties and regular Java System
properties.
Only one such element should be defined for a given application with the properties
that it needs. Several property placeholders can be configured as long as they have distinct
placeholder syntax ( If you need to modularize the source of properties used for the replacement, you should
not create multiple properties placeholders. Rather, you should create your own
|
You can use the
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 |
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 Infra 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"/>
Customizing Instantiation Logic with a FactoryBean
You can implement the cn.taketoday.beans.factory.FactoryBean
interface for objects that
are themselves factories.
The FactoryBean
interface is a point of pluggability into the Infra 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.
The FactoryBean
concept and interface are used in a number of places within the Infra
Framework. More than 50 implementations of the FactoryBean
interface ship with Infra
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.