blog.broncotoxique.com

Juste another geek’s website

Spring – BeanPostProcessor

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 implement PriorityOrdered,
  • the BeanPostProcessors that implement Ordered,
  • the BeanPostProcessors that does not implement Ordered,
  • 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.

Posted

in

by

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *