preface

In the last article we talked about how the custom implementation of SPI is integrated with Spring, but there was a small detail in the implementation process, which was that our SPI originally came with an interceptor. For those interested in implementing an SPI with interceptors, check out this article -> how to implement an SPI with interceptors.

In order to preserve the interceptor function, my original idea was to integrate the interceptor function into the spring backend processor. The implementation code was as follows

@Slf4j
@Deprecated
public class SpiInstancePostProcessor implements BeanPostProcessor {

    private DefaultListableBeanFactory beanFactory;

    private InterceptorHandler interceptorHandler;

    public SpiInstancePostProcessor(InterceptorHandler interceptorHandler,DefaultListableBeanFactory beanFactory) {
        this.interceptorHandler = interceptorHandler;
        this.beanFactory = beanFactory;
    }


    @SneakyThrows
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if(bean.getClass().isAnnotationPresent(Activate.class)){
            return interceptorHandler.getInterceptorChain().pluginAll(bean);
        }
        returnbean; }}Copy the code

The functionality is implemented, but the following message appears on the console

trationDelegate$BeanPostProcessorChecker : Bean ‘interceptorHandler’ of type [com.github.lybgeek.spring.interceptor.handler.InterceptorHandler] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)

The screening process

At that time the investigation is through the control of the prompt information, find the corresponding source. in

org.springframework.context.support.PostProcessorRegistrationDelegate
Copy the code

Find the corresponding implementation

@Override
		public Object postProcessAfterInitialization(Object bean, String beanName) {
			if(! (beaninstanceofBeanPostProcessor) && ! isInfrastructureBean(beanName) &&this.beanFactory.getBeanPostProcessorCount() < this.beanPostProcessorTargetCount) {
				if (logger.isInfoEnabled()) {
					logger.info("Bean '" + beanName + "' of type [" + bean.getClass().getName() +
							"] is not eligible for getting processed by all BeanPostProcessors " +
							"(for example: not eligible for auto-proxying)"); }}return bean;
		}

Copy the code

Seeing this message, it would be normal to let

! (beaninstanceofBeanPostProcessor) && ! isInfrastructureBean(beanName) &&this.beanFactory.getBeanPostProcessorCount() < this.beanPostProcessorTargetCount
Copy the code

This statement block is false, and it’s easy to see from the code that the statement block is false, with several entries

  1. ! (bean instanceof BeanPostProcessor)
  2. ! isInfrastructureBean(beanName)
  3. this.beanFactory.getBeanPostProcessorCount() <

    this.beanPostProcessorTargetCount

1 and 3 don’t seem to have a lot of room to play with, so we can look at the code block for 2 and 2 as follows

	private boolean isInfrastructureBean(@Nullable String beanName) {
			if(beanName ! =null && this.beanFactory.containsBeanDefinition(beanName)) {
				BeanDefinition bd = this.beanFactory.getBeanDefinition(beanName);
				return (bd.getRole() == RootBeanDefinition.ROLE_INFRASTRUCTURE);
			}
			return false; }}Copy the code

We can see that in the code

bd.getRole() == RootBeanDefinition.ROLE_INFRASTRUCTURE
Copy the code

This sentence is the core, but ROLE_INFRASTRUCTURE this is what ghost, we continue to track

	/**
	 * Role hint indicating that a {@code BeanDefinition} is providing an
	 * entirely background role and has no relevance to the end-user. This hint is
	 * used when registering beans that are completely part of the internal workings
	 * of a {@link org.springframework.beans.factory.parsing.ComponentDefinition}.
	 */
	int ROLE_INFRASTRUCTURE = 2;
Copy the code

This means that when this role is declared, the bean does not belong to an external user, but to spring itself. In other words, this bean is an official bean, not a crowd bean. This is simply an identity, so when we inject the bean into the Spring container, we can do the following

    @Bean
    @Role(RootBeanDefinition.ROLE_INFRASTRUCTURE)
    public InterceptorHandler interceptorHandler(final ObjectProvider<List<Interceptor>> listObjectProvider) {
        return new InterceptorHandler(interceptorChain(listObjectProvider));
    }
Copy the code

When combined with

 @Role(RootBeanDefinition.ROLE_INFRASTRUCTURE)
Copy the code

After that note, the world was cleared up and the console never appeared again

not eligible for getting processed by all BeanPostProcessors
Copy the code

But is the problem really solved?

Answer for anybody, and many times it is easy to cheat a person, but it’s hard to cheat, this man I good like you tell sister I rich, sister believe, wait your sister to buy her some valuable things, find that you can’t afford to buy, in order to win the favor of sister, you shall not hit swollen face depth, consumption in advance you simply can’t afford to buy things, It prevents you from spending on all the other things you could have

In the Spring world as well, BeanPostProcessor itself is a Bean. In general, it instantiates beans earlier than normal beans, but it now has some requirements for the beans you implement, that is, BeanPostProcessor sometimes relies on some beans. This leads to the possibility of some common beans being instantiated earlier than the BeanPostProcessor, leading to situations such as those pre-initialized beans not being able to enjoy some post-processor extension functionality

Therefore, for the case of this paper, use

 @Role(RootBeanDefinition.ROLE_INFRASTRUCTURE)
Copy the code

It doesn’t really address the nature of the problem, but since the interceptor in this article doesn’t require some of the subsequent Spring features, this solution is a good one.

Is there another solution? Yes, there is. We can use it

org.springframework.beans.factory.SmartInitializingSingleton
Copy the code

This class, he has a class in it

AfterSingletonsInstantiated ()Copy the code

Method, which serves as the callback interface after all singleton beans have been initialized. The following example uses this interface for the rest of this article

public class SpiInstanceInitializingSingleton implements SmartInitializingSingleton.BeanFactoryAware {

    private DefaultListableBeanFactory beanFactory;

    private InterceptorHandler interceptorHandler;

    public SpiInstanceInitializingSingleton(InterceptorHandler interceptorHandler) {
        this.interceptorHandler = interceptorHandler;
    }

    @Override
    public void afterSingletonsInstantiated(a) { changeBeanInstance2ProxyInstance(); }}Copy the code

You can also use Spring’s listening mechanisms, such as listening for Refresh events for processing

conclusion

This article is a bit of an extension to the custom implementation of SPI and how to integrate it with Spring

The demo link

Github.com/lyb-geek/sp…