Problem
You would like to register your own plug-ins in the Spring IoC container to process the bean instances during construction.
Solution
A bean post processor allows additional bean processing before and after the initialization callback method. The main characteristic of a bean post processor is that it will process all the bean instances in the IoC container one by one, not just a single bean instance. Typically, bean post processors are used for checking the validity of bean properties or altering bean properties according to particular criteria. The basic requirement of a bean post processor is to implement the BeanPostProcessor interface. You can process every bean before and after the initialization callback method by implementing the postProcessBeforeInitialization() and postProcessAfterInitialization() methods. Then Spring will pass each bean instance to these two methods before and after calling the initialization callback method, as illustrated in the following list:
1. Create the bean instance either by a constructor or by a factory method.
2. Set the values and bean references to the bean properties.
3. Call the setter methods defined in the aware interfaces.
4. Pass the bean instance to the postProcessBeforeInitialization() method of each bean post processor.5. Call the initialization callback methods.
6. Pass the bean instance to the postProcessAfterInitialization() method of each bean post processor.7. The bean is ready to be used.
8. When the container is shut down, call the destruction callback methods.
When using a bean factory as your IoC container, bean post processors can only be registered programmatically, or more accurately, via the addBeanPostProcessor() method. However, if you are using an application context, the registration will be as simple as declaring an instance of the processor in the bean configuration file, and then it will get registered automatically.
How It Works
Suppose you would like to ensure that the logging path of Cashier exists before the logging file is open. This is to avoid FileNotFoundException. As this is a common requirement for all components that require storage in the file system, you had better implement it in a general and reusable manner. A bean post processor is an ideal choice to implement such a feature in Spring.
First of all, for the bean post processor to distinguish which beans should be checked, you create a marker interface, StorageConfig, for your target beans to implement. Moreover, for your bean post processor to check for path existence, it must be able to access the path property. This can be done by adding the getPath() method to this interface.
package com.shop; public interface StorageConfig { public String getPath(); }
Then you should make the Cashier class implement this marker interface. Your bean post processor will only check the beans that implement this interface.
package com.shop; … public class Cashier implements BeanNameAware, StorageConfig { … public String getPath() { return path; } }
Now you are ready to write a bean post processor for path checking. As the best time to perform path checking is before the file is opened in the initialization method, you implement the postProcessBeforeInitialization() method to performthe checking.
package com.shop; … import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; public class PathCheckingBeanPostProcessor implements BeanPostProcessor { public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof StorageConfig) { String path = ((StorageConfig) bean).getPath(); File file = new File(path); if (!file.exists()) { file.mkdirs(); } } return bean; } public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } }
During bean construction, the Spring IoC container will pass all the bean instances to your bean post processor one by one, so you must filter the beans by checking the marker interface StoreConfig. If a bean implements this interface, you can access its path property by the getPath() method and check for its existence in the file system. If that path doesn’t exist, just create it with the File.mkdirs() method. Both the postProcessBeforeInitialization() and postProcessAfterInitialization() methods must return an instance for the bean being processed. That means you may even replace the original bean instance with a brand-new instance in your bean post processor. Remember that you must return the original bean instance even though you do nothing in the method. To register a bean post processor in an application context, just declare an instance of itin the bean configuration file. The application context will be able to detect which bean implements the BeanPostProcessor interface and register it to process all other bean instances in the container.
<beans …> … <bean class="com.shop.PathCheckingBeanPostProcessor" /> <bean id="cashier1" class="com.shop.Cashier" init-method="openFile" destroy-method="closeFile"> … </bean> </beans>
Note that if you specify the initialization callback method in the init-method attribute, or if you implement the InitializingBean interface, your PathCheckingBeanPostProcessor will work fine because it will process the cashier bean before the initialization method is called. However, if the cashier bean relies on the JSR-250 annotations @PostConstruct and @PreDestroy, and also a CommonAnnotationBeanPostProcessor instance to call the initialization method, your PathCheckingBeanPostProcessor will not work properly. This is because your bean post processor has a lower priority than CommonAnnotationBeanPostProcessor by default. As a result, the initialization method will be called before your path checking.
<beans …> … <bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor" /> <bean class="com.shop.PathCheckingBeanPostProcessor" /> <bean id="cashier1" class="com.shop.Cashier"> … </bean> </beans>
To define the processing order of bean post processors, you can have them implement the Ordered or PriorityOrdered interface, and return their order in the getOrder() method. Lower value returned by this method represents higher priority, and the order value returned by the PriorityOrdered interface will always precede that returned by the Ordered interface. As CommonAnnotationBeanPostProcessor implements the PriorityOrdered interface, your PathCheckingBeanPostProcessor must also implement this interface to have a chance to precede it.
package com.shop; … import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.core.PriorityOrdered; public class PathCheckingBeanPostProcessor implements BeanPostProcessor, PriorityOrdered { private int order; public int getOrder() { return order; } public void setOrder(int order) { this.order = order; } … }
Now in the bean configuration file, you should assign a lower order value to your PathCheckingBeanPostProcessor for it to check and create the path of the cashier bean before its initialization method is called by CommonAnnotationBeanPostProcessor. As the default order of CommonAnnotationBeanPostProcessor is Ordered.LOWEST_PRECEDENCE, you can simply assign a zero order value to your PathCheckingBeanPostProcessor.
<beans …> … <bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor" /> <bean class="com.shop.PathCheckingBeanPostProcessor"> <property name="order" value="0" /> </bean> <bean id="cashier1" class="com.shop.Cashier"> <property name="path" value="c:/cashier" /> </bean> </beans>
As zero is the default order value of your PathCheckingBeanPostProcessor, you can simply omit this setting. Moreover, you can continue to use <context:annotation-config> to get CommonAnnotationBeanPostProcessor registered automatically.
<beans …> … <context:annotation-config /> <bean class="com.shop.PathCheckingBeanPostProcessor" /> </beans>