1. 程式人生 > >使用@import導入實現了ImportBeanDefinitionRegistrar接口的類,不能被註冊為bean

使用@import導入實現了ImportBeanDefinitionRegistrar接口的類,不能被註冊為bean

sage lur watch ref java throw 根據 lib spa

今天在調試公司spring項目的時候發現了這樣一個問題,由於我們的項目使用的是springboot就以springboot為例,代碼如下:

@Import({DataSourceRegister.class,A.class})
@SpringBootApplication
@ComponentScan("com.viewhigh.bi")
//@EnableCaching
public class BiApplication {
    public static void main(String[] args) {
        LogUtil.setEnabled(true);//開啟日誌輸出
SpringApplication sa = new SpringApplication(BiApplication.class); sa.setBannerMode(Banner.Mode.LOG); sa.run(args); } }

springboot啟動的時候,loder模塊會根據“清單文件”加載該BIApplication類,並反射調用psvm入口函數main,但是一個很有意思的問題出現了,項目正常運行之後,在springcontext中可以找到Bean類A,但是無法找到DataSourceRegister這個類;

我們知道在spring4.2以後@Import註解也可以導入一個常規類,並將其註冊為bean,那麽為什麽DataSourceRegister沒有被註冊為Bean呢?

DataSourceRegister類是用來進行初始化數據源和並提供了執行動態切換數據源的工具類

public class DataSourceRegister<T> implements EnvironmentAware, ImportBeanDefinitionRegistrar {
    private javax.sql.DataSource defaultTargetDataSource;
    
static final String MAINDATASOURCE = "mainDataSource"; public final void setEnvironment(Environment environment) { DruidEntity druidEntity = FileUtil.readYmlByClassPath("db_info", DruidEntity.class); defaultTargetDataSource = DataSourceUtil.createMainDataSource(druidEntity); } public final void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) { // 0.將主數據源添加到數據源集合中 DataSourceSet.putTargetDataSourcesMap(MAINDATASOURCE, defaultTargetDataSource); //1.創建DataSourceBean GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(DataSource.class); beanDefinition.setSynthetic(true); MutablePropertyValues mpv = beanDefinition.getPropertyValues(); //spring名稱約定為defaultTargetDataSource和targetDataSources mpv.addPropertyValue("defaultTargetDataSource", defaultTargetDataSource); mpv.addPropertyValue("targetDataSources", DataSourceSet.getTargetDataSourcesMap()); beanDefinitionRegistry.registerBeanDefinition("dataSource", beanDefinition); } }

看到代碼後相信大家已經明白了,這個動態數據註冊類實現了ImportBeanDefinitionRegistrar 接口,沒錯就是這個原因,由於實現了該接口讓該類成為了擁有註冊bean的能力。從原理上也能說得通作為一個Bean的註冊類是沒有必要和A類一樣都被註冊為Bean的!

雖然這樣解釋也不為過但我仍然想一探究竟,本來想大概找找spring涉及關鍵類如:ConfigurationClass,ConfigurationClassParser等,可能由於不熟悉看到類中的代碼就呵呵了,似乎無從下手!

所以準備調試下spring啟動部分的代碼 ,這樣會更清晰些!(以spring boot V1.5.6為例)

======

spring boot啟動時使用了SpringApplication類的run方法來牽引整個spring的初始化過程!!!

沒錯了就是從run開始吧!

代碼如下:

public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        FailureAnalyzers analyzers = null;
        this.configureHeadlessProperty();
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting();

        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            Banner printedBanner = this.printBanner(environment);
            context = this.createApplicationContext();
            new FailureAnalyzers(context);
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            this.refreshContext(context);
            this.afterRefresh(context, applicationArguments);
            listeners.finished(context, (Throwable)null);
            stopWatch.stop();
            if(this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }

            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, listeners, (FailureAnalyzers)analyzers, var9);
            throw new IllegalStateException(var9);
        }
    }

手一懶,就把整個方法直接copy了。這就是springboot在啟動時的完整足跡。。。閑話少說我們直擊關鍵點;

context = this.createApplicationContext();
this.refreshContext(context);

這兩段是最關鍵的地方,閱讀過一些spring書籍的兄弟都知道refreshContext就是在做spring運行後的初始化工作。那麽在createApplicationContext當中,由於我們是web項目,則spring默認給我們創建了一個AnnotationConfigEmbeddedWebApplicationContext

當然它也是繼承GenericWebApplicationContext類和GenericApplicationContext類的,那麽他默認會持有一個DefaultListableBeanFactory對象,這個對象可以用來創建Bean,吼吼,這個塊說的似乎沒有意義哈!!!

技術分享圖片

接著往下走,進入refreshContext中會調用一系列的refresh方法,最終進入AbstractApplicationContext中:

@Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);

                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);

                // Initialize message source for this context.
                initMessageSource();

                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                onRefresh();

                // Check for listener beans and register them.
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.
                finishRefresh();
            }

            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
                }

                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

                // Reset ‘active‘ flag.
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            }

            finally {
                // Reset common introspection caches in Spring‘s core, since we
                // might not ever need metadata for singleton beans anymore...
                resetCommonCaches();
            }
        }
    }

invokeBeanFactoryPostProcessors()方法就是Bean在註冊前期做的一系列數據收集工作!

跟著堆棧繼續深入,會進入到這個方法中,這個方法就是初始化bean前的所有軌跡:

技術分享圖片

在invokeBeanFactoryPostProcessors方法中繼續跟進一系列方法就會看到在一開始的時候spring會初始化幾個系統固有的Bean:

技術分享圖片

使用@import導入實現了ImportBeanDefinitionRegistrar接口的類,不能被註冊為bean