BeanPostProcessor
is a factory hook that allows the Spring framework to customize and modify Bean instances when they are newly created. For example: by checking it’s marked interface or using a proxy to wrap it. The application context will automatically detect BeanPostProcessors
from the Bean definition and apply them to any beans created subsequently.
On projects, customized BeanPostProcessors
is used specially to log data access operations for example :
@Component
public class DatasourceProxyBeanPostProcessor
implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof DataSource && !(bean instanceof ProxyDataSource)) {
// Instead of directly returning a less specific datasource bean
// (e.g.: HikariDataSource -> DataSource), return a proxy object.
// See following links for why:
// https://stackoverflow.com/questions/44237787/how-to-use-user-defined-database-proxy-in-datajpatest
// https://gitter.im/spring-projects/spring-boot?at=5983602d2723db8d5e70a904
// http://blog.arnoldgalovics.com/2017/06/26/configuring-a-datasource-proxy-in-spring-boot/
final ProxyFactory factory = new ProxyFactory(bean);
factory.setProxyTargetClass(true);
factory.addAdvice(new ProxyDataSourceInterceptor((DataSource) bean));
return factory.getProxy();
}
return bean;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean;
}
private static class ProxyDataSourceInterceptor implements MethodInterceptor {
private final DataSource dataSource;
private final LonscapeQueryListener ql = new LonscapeQueryListener();
public ProxyDataSourceInterceptor(final DataSource dataSource) {
ql.setQueryLogEntryCreator(new DefaultJsonQueryLogEntryCreator());
this.dataSource = ProxyDataSourceBuilder.create(dataSource).name("ALPSdatasource").asJson().listener(ql)
.build();
}
@Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
final Method proxyMethod = ReflectionUtils.findMethod(dataSource.getClass(),
invocation.getMethod().getName());
if (proxyMethod != null) {
return proxyMethod.invoke(dataSource, invocation.getArguments());
}
return invocation.proceed();
}
}
In the Spring IoC container, when a bean is instantiated, the method postProcessAfterInitialization
is called to, in our case, to wrap the Bean “DataSource
” with a proxy which initiates a database query listener in order to log the database access.
BeanPostProcessor
itself is also a Bean. Generally speaking, it’s instantiation time is earlier than ordinary Beans, but BeanPostProcessor
also depends on some Beans
, which causes some Beans
to be instantiated earlier than BeanPostProcessor
, which will cause some problems.
Conflict with @EnableGlobalMethodSecurity
To secure the CRUD methods on application, it’s needed to create a PermissionEvaluator
which, on Spring Security config side, needs an annotation of @EnableGlobalMethodSecurity
. However, when EnableGlobalMethodSecurity
is annotated on the config class, the query listener has never been trigered, as well logs lost.
Returned to DatasourceProxyBeanPostProcessor, and debugs show: DataSource
bean is no longer intercepted. Not only this bean, about 100 Beans are lost in DatasourceProxyBeanPostProcessor
, so not intercepted and processeded by this BeanPostPorcessor
.
By exchange, I got the following log:
When “ORDER” means everything…
So the first suspicion is that DataSource
has not been enhanced by the proxy of DatasourceProxyBeanPostProcessor
. Debugging found that when the Bean of DataSource
is instantiated when the EnableGlobalMethodSecurity
is dependent, the PostBeanProcessor
used to process the Bean is obviously less than when it is not dependent on EnableGlobalMethodSecurity
, and it does not contain InfrastructureAdvisorAutoProxyCreator
.
That’s why we got:
'dataSource' of type [com.zaxxer.hikari.HikariDataSource] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)"
We found InstantiationAwareBeanPostProcessor
, SmartInstantiationAwareBeanPostProcessor
, AbstractAutoProxyCreator
, InfrastructureAdvisorAutoProxyCreator
in the implementation class of BeanPostProcessor
Based on this inference, it may be that the DataSource
Bean was started too early, which caused the BeanPostProcessor
(dataProxy BeanPostProcessor
) later to have no time to instantiate and be registered in the BeanFactory
. Because the dependency of @EnableGlobalMethodSecurity -> InfrastructureAdvisorAutoProxyCreator -> DataSource caused it to be instantiated in advance.
Thanks to an article of Dave Syer, it’s easier to understand why. But no solutions are mentionned to resolve the problem.
After hard debugging, we finally understood the start time of BeanPostProcessor
. A list of BeanPostProcessors
is maintained in AbstractBeanFactory
:
private final List<BeanPostProcessor> beanPostProcessors = new ArrayList<BeanPostProcessor>();
And implement the method defined by ConfigurableBeanFactory:
void addBeanPostProcessor(BeanPostProcessor beanPostProcessor);
Finally, call in the registerBeanPostProcessors method, register all BeanPostProcessor
PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this);
In this method, first register BeanPostProcessorChecker
:
beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));
The BeanPostProcessorChecker
will check the number of BeanPostProcessors
that can work on the current Bean and the total number of BeanPostProcessors
after the Bean is created. If the number of functions is less than the total, it will log the message we saw above.
The following two images show the DataSource
Bean’s eligible Processors with and without @EnableGlobalMethodSecurity
configuration
With the @EnableGlobalMethodSecurity
configuration, it will lose several processors, among which “DataProxyBeanPostProcessor
“
Back to PostProcessorRegistrationDelegate.registerBeanPostProcessors
:
All BPP (BeanPostProcessor) are classed in 4 types:
In their orderof instantiation and register
- the
BeanPostProcessors
that implementPriorityOrdered
, - the
BeanPostProcessors
that implementOrdered
, - the
BeanPostProcessors
that does not implementOrdered
, internalPostProcessors
the code is as follows:
// Separate between BeanPostProcessors that implement PriorityOrdered,
// Ordered, and the rest.
List priorityOrderedPostProcessors = new ArrayList();
List internalPostProcessors = new ArrayList();
List orderedPostProcessorNames = new ArrayList();
List nonOrderedPostProcessorNames = new ArrayList();
for (String ppName : postProcessorNames) {
if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
priorityOrderedPostProcessors.add(pp);
if (pp instanceof MergedBeanDefinitionPostProcessor) {
internalPostProcessors.add(pp);
}
}
else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
orderedPostProcessorNames.add(ppName);
}
else {
nonOrderedPostProcessorNames.add(ppName);
}
}
// First, register the BeanPostProcessors that implement PriorityOrdered.
sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors);
// Next, register the BeanPostProcessors that implement Ordered.
List orderedPostProcessors = new ArrayList();
for (String ppName : orderedPostProcessorNames) {
BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
orderedPostProcessors.add(pp);
if (pp instanceof MergedBeanDefinitionPostProcessor) {
internalPostProcessors.add(pp);
}
}
sortPostProcessors(orderedPostProcessors, beanFactory);
registerBeanPostProcessors(beanFactory, orderedPostProcessors);
// Now, register all regular BeanPostProcessors.
List nonOrderedPostProcessors = new ArrayList();
for (String ppName : nonOrderedPostProcessorNames) {
BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
nonOrderedPostProcessors.add(pp);
if (pp instanceof MergedBeanDefinitionPostProcessor) {
internalPostProcessors.add(pp);
}
}
registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors);
// Finally, re-register all internal BeanPostProcessors.
sortPostProcessors(internalPostProcessors, beanFactory);
registerBeanPostProcessors(beanFactory, internalPostProcessors);
// Re-register post-processor for detecting inner beans as ApplicationListeners,
// moving it to the end of the processor chain (for picking up proxies etc).
beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
And unfortunately, the DataProxyBeanPostProcessor
is a nonOrderedPostProcessor
, it will be instantiated after DataSource
Bean
, that’s Why.
SOLUTION
Now, We understand the basic raison, one easy solution: why not make DataProxyBeanPostProcessor
an OrderedPostProcessor
? That’s what we do and it works. Code cannot be simpler.
public class DatasourceProxyBeanPostProcessor implements BeanPostProcessor, Ordered
and over write the method:
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 1;
}
Thinking
As Dave Syer mentionned in his article, the early instantiation is very aggressive and could be dangerous for application. A better way should be to Bean proxying when it’s instantiated by modifying the configuration to avoid all early instantiation which could cause other security and side effects. Other talked about @Lazy annotation to delay the instantiation moment, it could obviously be a reliable solution, but no effect on EnableGlobalMethodSecurity as I tried Or waiting for Spring updating to make the whole framework securer
A big thank to my colleague Farouk B. for writing this article.
Leave a Reply