1. Overview
In this tutorial, we're going to investigate the BeanPostProcessor class of Spring framework.
Spring uses the BeanPostProcessor implementations extensively. Also, it promotes its usage as an extension point to the framework.
2. Why BeanPostProcessor?
Let's first look at what BeanPostProcessor is and how it helps us.
Firstly, the BeanPostProcessor interface enables us to hook into a bean's initialization. Because Spring invokes its methods before and after the bean initialization:
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;
}
}
In this regard, BeanPostProcessor acts as an extension point. By using it, we can wire additional dependencies or create a proxy around our bean. For example, Spring uses ApplicationContextAwareProcessor to process the beans implementing the Aware interfaces. Additionally, Spring relies on AutowiredAnnotationBeanPostProcessor to auto-wire annotated fields, setter methods or custom config methods.
3. A Simple BeanPostProcessor Implementation
Let's see how we can create a BeanPostProcessor implementation.
For our sample implementation, we'll work with the GreetingService interface:
public interface GreetingService {
void hello();
}
Our BeanPostProcessor class will print a log statement whenever Spring encounters a bean implementing GreetingService:
@Component
public class GreetingBeanPostProcessor implements BeanPostProcessor {
@Nullable
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof GreetingService) {
System.out.printf("postProcessBeforeInitialization() in %s for %s%n", getClass().getSimpleName(), beanName);
}
return bean;
}
@Nullable
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof GreetingService) {
System.out.printf("postProcessAfterInitialization() in %s for %s%n", getClass().getSimpleName(), beanName);
}
return bean;
}
}
Here, we're checking the bean type. If it is an instance of GreetingService, we're printing the class name and the bean name.
In order for Spring to pick up our GreetingBeanPostProcessor, we just need to mark it as a bean.
4. Logging Interceptor using BeanPostProcessor
Next, let's introduce a more involved implementation to showcase the power of BeanPostProcessor.
For this purpose, we'll implement a BeanPostProcessor that wraps the actual bean instance with a proxy:
@Component
public class LoggingBeanPostProcessor implements BeanPostProcessor {
@Nullable
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof GreetingService) {
return proxiedBean((GreetingService) bean);
}
return bean;
}
private Object proxiedBean(GreetingService bean) {
ProxyFactory proxyFactory = new ProxyFactory(bean);
proxyFactory.addAdvice(new LoggingInterceptor());
return proxyFactory.getProxy();
}
private static class LoggingInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("Before greeting");
Object returnValue = methodInvocation.proceed();
System.out.println("After greeting");
return returnValue;
}
}
}
Here, we're using Spring AOP features to create a proxy around GreetingService implementations. More specifically, we're creating LoggingInterceptor which is an implementation of MethodInterceptor. This interceptor will print a log statement before and after every method invocation. Then with the help of ProxyFactory, we're wrapping our actual bean instance with a proxy.
5. Order
Lastly, we can define multiple BeanPostProcessor implementations in our application. If the ordering of these processors is important, we should tell Spring to order them.
Firstly we can use the Ordered interface:
@Component
public class GreetingBeanPostProcessor implements BeanPostProcessor, Ordered {
@Override
public int getOrder() {
return 0;
}
// Other methods
}
Secondly, we can use the @Order annotation:
@Component
@Order(0)
public class GreetingBeanPostProcessor implements BeanPostProcessor {
// Methods
}
6. Summary
In this tutorial, we've looked at how we can use the BeanPostProcessor interface.
Check out the source code for all examples over on Github.