排查not eligible for getting processed by all BeanPostProcessors

linyb極客之路發表於2021-11-16

前言

上一篇文章我們聊了一下自定義實現的SPI如何與spring進行整合,但其實在實現的過程中有個小細節,就是原先我們的SPI是帶了攔截器功能,(ps:對如何實現一個帶攔截器的SPI感興趣的朋友,可以檢視這篇文章-->聊聊如何實現一個帶有攔截器功能的SPI)。

為了保留這個攔截器功能,我原先的想法是狸貓換太子,在spring提供的後置處理器裡面,把攔截器功能給整合進去,當時的實現程式碼如下

@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;
    }

}

功能是實現了,但是控制檯卻出現如下資訊

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)

排查過程

當時排查是通過控制提示的資訊,找到對應原始碼。在

org.springframework.context.support.PostProcessorRegistrationDelegate

找到相應的實現

@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;
        }

看到這個資訊,按正常做法是讓

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

這個語句塊為false,從程式碼我們很容易看出語句塊為false,有幾個入口

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

1和3看起來是沒多大發揮空間,我們可以看下2,2的程式碼塊如下

    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;
        }
    }

從程式碼我們可以看出

bd.getRole() == RootBeanDefinition.ROLE_INFRASTRUCTURE

這句話是核心,然而ROLE_INFRASTRUCTURE這個又是什麼鬼,我們繼續跟蹤

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

這句話表達的意思是當宣告這個角色時,這個bean就不是歸屬外部使用者,而是歸屬spring內部。換句話說就是這個bean是個官方bean,不是群眾bean。總之這個就是一個身份標識,因此我們在把bean注入到spring容器中,就可以做如下處理

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

當加上

 @Role(RootBeanDefinition.ROLE_INFRASTRUCTURE)

這個註解後,世界果然清淨了,控制檯再也沒有出現

not eligible for getting processed by all BeanPostProcessors
但問題真的就解決了嗎?

答案見仁見智,很多時候我們很容易欺騙一個人一時,但卻很難欺騙這個人一世,好比如你告訴妹子我家財萬貫,妹子相信了,等到妹子要你給她買一些貴重的東西時,發現你買不起,你為了討妹子歡心,不得以打腫臉充胖子,提前消費你壓根買不起的東西,導致你後面沒法再消費其他你本可以消費的東西

在spring世界也是如此,BeanPostProcessor本身也是一個Bean,一般而言其例項化時機要早過普通的Bean,但是她現在對於你實現的bean有一些需求,即BeanPostProcessor有時也會依賴一些Bean,這就導致了一些普通Bean的例項化早於BeanPostProcessor的可能情況,而引發一些情況,比如這些提前初始化的bean無法享有一些後置處理器擴充套件的功能

因此對本文的案例,用

 @Role(RootBeanDefinition.ROLE_INFRASTRUCTURE)

其實是沒解決問題本質,但是因為本文的攔截器不需要後續的一些spring特色功能,因此這種解法也算是一種吧。

那有沒有其他解法,答案是有的,我們可以利用

org.springframework.beans.factory.SmartInitializingSingleton

這個類,他這個類裡面有個

afterSingletonsInstantiated()

方法,這個方法的作用是在所有單例bean初始化完成呼叫後的回撥介面。本文後面的例子就是改用這個介面實現,程式碼如下

public class SpiInstanceInitializingSingleton implements SmartInitializingSingleton,BeanFactoryAware {

    private DefaultListableBeanFactory beanFactory;

    private InterceptorHandler interceptorHandler;

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

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

    }
    }

當然還可以使用spring的監聽機制,比如監聽refresh事件進行處理

總結

本文算是自定義實現的SPI如何與spring進行整合這篇文章的一點擴充套件補充

demo連結

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

相關文章