Not acceptable for getting processed by all beanpostprocessors

Posted by aneesme on Fri, 07 Jan 2022 03:50:18 +0100

01

preface

In the last article, we talked about how to integrate the custom implemented SPI with spring, but in fact, there is a small detail in the implementation process, that is, our spi was originally equipped with interceptor function, (ps: friends interested in how to implement an SPI with interceptor can view this article -- > Talk about how to implement an SPI with interceptor function).

In order to retain the interceptor function, my original idea was to change the crown prince and integrate the interceptor function in the post processor provided by spring. The implementation code at that time is 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);
        }
        return bean;
    }

}

The function is implemented, but the following information 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)

02

Troubleshooting process

At that time, the troubleshooting was to find the corresponding source code by controlling the prompt information. stay

org.springframework.context.support.PostProcessorRegistrationDelegate

Find the corresponding implementation

@Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
      if (!(bean instanceof BeanPostProcessor) && !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;
    }

When you see this message, it's normal to let

!(bean instanceof BeanPostProcessor) && !isInfrastructureBean(beanName) &&
          this.beanFactory.getBeanPostProcessorCount() < this.beanPostProcessorTargetCount

The statement block is false. We can easily see from the code that the statement block is false. There are several entries

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

1 and 3 don't seem to have much room to play. We can look at the code blocks of 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;
    }
  }

From the code, we can see

bd.getRole() == RootBeanDefinition.ROLE_INFRASTRUCTURE

This sentence is the core, however, role_ What the hell is this? We'll keep tracking

/**
   * 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;

This sentence means that when the role is declared, the bean does not belong to external users, but to spring. In other words, this bean is an official bean, not a mass bean. In short, this is an identity, so we can do the following when injecting beans into the spring container

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

When you add

@Role(RootBeanDefinition.ROLE_INFRASTRUCTURE)

After this annotation, the world was really clean, and the console never appeared again

not eligible for getting processed by all BeanPostProcessors

But is the problem really solved?

Different people have different opinions on the answer. In many cases, it is easy for us to deceive a person for a while, but it is difficult for us to deceive this person for a lifetime. For example, you tell your sister that my family is rich, and my sister believes it. When my sister asks you to buy her something valuable, you find that you can't afford it. In order to please my sister, you must not make a fat face and consume things you can't afford in advance, So you can't consume other things you could have consumed later

The same is true in the spring world. BeanPostProcessor itself is also a bean. Generally speaking, its instantiation time is earlier than that of ordinary beans, but now she has some requirements for the beans you implement, that is, BeanPostProcessor sometimes depends on some beans, which leads to the possibility that the instantiation of ordinary beans is earlier than that of BeanPostProcessor, causing some situations, For example, these pre initialized beans cannot enjoy some post processor extended functions

Therefore, for the case of this paper, use

@Role(RootBeanDefinition.ROLE_INFRASTRUCTURE)

In fact, it does not solve the essence of the problem, but because the interceptor in this paper does not need some subsequent spring features, this solution is also a kind of solution.

Is there any other solution? The answer is yes. We can use it

org.springframework.beans.factory.SmartInitializingSingleton

This class, he has one in this class

afterSingletonsInstantiated()

Method, which is used to call the callback interface after all singleton bean s are initialized. The example later in this article is to use this interface for implementation. The code is as follows

public class SpiInstanceInitializingSingleton implements SmartInitializingSingleton,BeanFactoryAware {

    private DefaultListableBeanFactory beanFactory;

    private InterceptorHandler interceptorHandler;

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

    @Override
    public void afterSingletonsInstantiated() {
        changeBeanInstance2ProxyInstance();

    }
    }

Of course, you can also use spring's listening mechanism, such as listening to refresh events for processing

03

summary

This article is Talk about how to integrate SPI with spring An extension of this article

04

demo link

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-spi-enhance/springboot-spi-framework-spring