3.8 Container extension points

Typically, an application developer does not need to subclass any ApplicationContext implementation classes. You can extend The Spring IoC container infinitely by plugging in implementations of special integration interfaces. The next few sections describe these integration interfaces.

3.8.1 Customizing beans using the BeanPostProcessor Interface

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 otherwise initializing a bean, you can plug in one or more BeanPostProcessor implementations.

You can configure multiple BeanPostProcessor interfaces. You can control the order in which these BeanPostProcessor interfaces execute 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 more details, consult the Javadoc for the BeanPostProcessor and Ordered interfaces.

[Note]Note

BeanPostProcessors operate on bean (or object) instances; that is to say, the Spring IoC container instantiates a bean instance and then BeanPostProcessor interfaces do their work.

BeanPostProcessor interfaces are scoped per-container. This is only relevant if you are using container hierarchies. If you define a BeanPostProcessor in one container, it will only do its work on the beans in that container. Beans that are defined in one container are not post-processed by a BeanPostProcessor in another container, even if both containers are part of the same hierarchy.

To change the actual bean definition (that is, the recipe that defines the bean), you instead need to use a BeanFactoryPostProcessor, described below in Section 3.8.2, “Customizing configuration metadata with BeanFactoryPostProcessor interface”.

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 afterPropertiesSet and any declared init method) are called, and also afterwards. 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 may wrap a bean with a proxy. Some Spring AOP infrastructure classes are implemented as bean post-processors and they do this proxy-wrapping logic.

An ApplicationContext automatically detects any beans that are defined in the configuration metadata it receives that implement the BeanPostProcessor interface. The ApplicationContext registers these beans as post-processors, to be then called appropriately by the container upon bean creation. You can then deploy the post-processors as you would any bean.

[Note]BeanPostProcessors and AOP auto-proxying

Classes that implement the BeanPostProcessor interface are special, and so they are treated differently by the container. All BeanPostProcessors and their directly referenced beans are instantiated on startup, as part of the special startup phase of the ApplicationContext. Next, all those BeanPostProcessors are registered in a sorted fashion - and applied to all further beans. Because AOP auto-proxying is implemented as a BeanPostProcessor itself, no BeanPostProcessors or directly referenced beans are eligible for auto-proxying, and thus do not have aspects woven into them.

For any such bean, you should see an info log message: Bean foo is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying)”.

The following examples show how to write, register, and use BeanPostProcessors in the context of an ApplicationContext.

3.8.1.1 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.

Find below the custom BeanPostProcessor implementation class definition:

package scripting;

import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.BeansException;

public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

    // simply return the instantiated bean as-is
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean; // we could potentially return any object reference here...
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Bean '" + beanName + "' created : " + bean.toString());
        return bean;
    }
}
<?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 
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://www.springframework.org/schema/lang 
           http://www.springframework.org/schema/lang/spring-lang-3.0.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 simply defined. It does not even have a name, and because it is a bean it can be dependency-injected just like any other bean. (The preceding configuration also defines a bean that is backed by a Groovy script. The Spring 2.0 dynamic language support is detailed in the chapter entitled Chapter 26, Dynamic language support.)

The following small driver script executes 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 = (Messenger) ctx.getBean("messenger");
        System.out.println(messenger);
    }
}

The output of the preceding execution resembles the following:

Bean 'messenger' created : [email protected]
[email protected]

3.8.1.2 Example: The RequiredAnnotationBeanPostProcessor

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 shown in the section entitled Section 27.2.1, “@Required” which demonstrates the usage of a custom BeanPostProcessor implementation that ships with the Spring distribution which ensures that JavaBean properties on beans that are marked with an (arbitrary) annotation are actually (configured to be) dependency-injected with a value.

3.8.2 Customizing configuration metadata with BeanFactoryPostProcessor interface

The next extension point that we will look at is the org.springframework.beans.factory.config.BeanFactoryPostProcessor. The semantics of this interface are similar to the BeanPostProcessor, with one major difference: BeanFactoryPostProcessors operate on the bean configuration metadata; that is, the Spring IoC container allows BeanFactoryPostProcessors to read the configuration metadata and potentially change it before the container instantiates any beans other than BeanFactoryPostProcessors.

You can configure multiple BeanFactoryPostProcessors. You can control the order in which these BeanFactoryPostProcessors execute 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; consult the Javadoc for the BeanFactoryPostProcessor and Ordered interfaces for more details.

[Note]Note

If you want to change the actual bean instances (the objects that are created from the configuration metadata), then you instead need to use a BeanPostProcessor (described above in the section entitled Section 3.8.1, “Customizing beans using the BeanPostProcessor Interface ”.

Also, BeanFactoryPostProcessors are scoped per-container. This is only relevant if you are using container hierarchies. If you define a BeanFactoryPostProcessor in one container, it will only do its stuff on the bean definitions in that container. Bean definitions in another container will not be post-processed by BeanFactoryPostProcessors in another container, even if both containers are part of the same hierarchy.

A bean factory post-processor is executed automatically when it is declared inside of an ApplicationContext, in order to apply changes to the configuration metadata that defines a container. Spring includes a number of pre-existing bean factory post-processors, such as PropertyOverrideConfigurer and PropertyPlaceholderConfigurer. A custom BeanFactoryPostProcessor can also be used, for example, to register custom property editors.

An ApplicationContext detects any beans that are deployed into it and that implement the BeanFactoryPostProcessor interface. It automatically uses these beans as bean factory post-processors, at the appropriate time. You can then deploy these post-processor beans as you would any other bean.

[Note]Note

As with BeanPostProcessors, you typically do not want BeanFactoryPostProcessors marked as lazy-initialized. If they are marked as such, the Spring container never instantiates them, and thus they cannot apply their custom logic. If you use the default-lazy-init attribute on the declaration of your <beans/> element, be sure to mark your various BeanFactoryPostProcessor bean definitions with lazy-init="false".

3.8.2.1 Example: the PropertyPlaceholderConfigurer

You use the PropertyPlaceholderConfigurer to externalize property values from a bean definition into another separate file in 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. The example shows properties configured from an external Properties file. At runtime, a PropertyPlaceholderConfigurer is applied to the metadata that will replace some properties of the DataSource. The values to replace are specified as 'placeholders' of the form ${property-name} which follows the Ant / Log4J / JSP EL style.

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations" value="classpath:com/foo/jdbc.properties"/>
    </property>
</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 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 string ${jdbc.username} is replaced at runtime with the value 'sa' and similarly for other placeholder values that match to keys in the property file. The PropertyPlaceholderConfigurer checks for placeholders in most locations of a bean definition and the placeholder prefix and suffix can be customized.

With the context namespace introduced in Spring 2.5, it is possible to configure property placeholders with a dedicated configuration element. You can provide multiple locations as a comma-separated list in the location attribute.

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

The PropertyPlaceholderConfigurer does not look for properties only in the Properties file you specify, but also checks against the Java System properties if it cannot find a property you are trying to use. You can customize this behavior by setting the systemPropertiesMode property of the configurer. It has three values that specify configurer behavior: always override, never override, and override only if the property is not found in the properties file specified. Consult the Javadoc for the PropertyPlaceholderConfigurer for more information.

[Tip]Class name substitution

You can use the PropertyPlaceholderConfigurer to substitute class names, which is sometimes useful when you have to pick a particular implementation class at runtime. For example:

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
        <value>classpath:com/foo/strategy.properties</value>
    </property>
    <property name="properties">
        <value>custom.strategy.class=com.foo.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 an ApplicationContext for a non-lazy-init bean.

3.8.2.2 Example: the PropertyOverrideConfigurer

The PropertyOverrideConfigurer, another bean factory post-processor, resembles the PropertyPlaceholderConfigurer, 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 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 this format:

beanName.property=value

For example:

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

This example file is usable against a container definition that contains a bean called dataSource, which 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 this example...

foo.fred.bob.sammy=123

... the sammy property of the bob property of the fred property of the foo bean is set to the scalar value 123.

[Note]Note

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:

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

3.8.3 Customizing instantiation logic with the FactoryBean Interface

You 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 interface provides three methods:

  • Object 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(): returns true if this FactoryBean returns singletons, false otherwise.

  • Class getObjectType(): returns the object type returned by the getObject() method or null if the type is not known in advance

The FactoryBean concept and interface is 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, not the bean it produces, you preface the bean id with the ampersand symbol & (without quotes) 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, and invoking getBean("&myBean") returns the FactoryBean instance itself.