深入理解Spring —— Bean 何時被創建

Bean 何時被創建

    Spring 中的一個Bean 是何時被創建的呢?如何你對此疑問,可以通過以此篇文章的做法去了解一個Bean 在Spring中是如何被創建的。

環境準備

1.pom文件

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>1.5.1.RELEASE</version>
        </dependency>
    </dependencies>

 

2.啓動類

@SpringBootApplication
@ComponentScan(value = "com.seven")
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class,args);
    }
}

  

3.一個Bean

@Controller
public class TestController {
    public TestController(){
        System.out.println("TestController 創建了");
    }
}

 

調試

1.直接啓動,通過日誌可以看到TestController 被創建了

2018-07-02 17:38:58.083  INFO 7076 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Mapping servlet: 'dispatcherServlet' to [/]
2018-07-02 17:38:58.088  INFO 7076 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
2018-07-02 17:38:58.088  INFO 7076 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2018-07-02 17:38:58.089  INFO 7076 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2018-07-02 17:38:58.089  INFO 7076 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]
TestController 創建了
2018-07-02 17:38:58.483  INFO 7076 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@78dd667e: startup date [Mon Jul 02 17:38:55 CST 2018]; root of context hierarchy
2018-07-02 17:38:58.558  INFO 7076 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2018-07-02 17:38:58.559  INFO 7076 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2018-07-02 17:38:58.587  INFO 7076 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-07-02 17:38:58.587  INFO 7076 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]

   

2.設置斷點進行調試

    這一步是關鍵的,在進行操作的時候,可根據自己的情況進行斷點調試,這邊給出幾個推薦的斷點位置

斷點一:啓動位置

public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class,args);
    }
}

 

斷點二: run 方法內部
進入run 方法裏面找到這個方法

public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        FailureAnalyzers analyzers = null;
        configureHeadlessProperty();
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
            Banner printedBanner = printBanner(environment);
            context = createApplicationContext();
            analyzers = new FailureAnalyzers(context);
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);
            refreshContext(context);
            afterRefresh(context, applicationArguments);
            listeners.finished(context, null);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
            return context;
        }
        catch (Throwable ex) {
            handleRunFailure(context, listeners, analyzers, ex);
            throw new IllegalStateException(ex);
        }
    }

 

該方法就是Spring Boot項目啓動時 所要執行的代碼,根據閱讀可以分爲以下步驟:
1. 加載環境變量
2. 創建上下文 ConfigurableApplicationContext 對象
3. 準備上下文 prepareContext()
4. 刷新上下文 refreshContext()
5. 刷新之後的處理 afterRefresh()
可以在以上的五個地方進行斷點設置,然後開始調試,結合控制檯的日誌一步一步觀察。

通過斷點調試,發現執行完refreshContext()方法執行完畢之後,日誌打印了

TestController 創建了

    1

初步判斷,Bean在執行refreshContext() 方法的時候創建的,那麼接下來我們看一下refreshContext這個方法幹了什麼事情?

斷點三:
進入refreshContext 方法,找到最終調用的方法 refresh()

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

   

以步驟二的斷點設置方式,我們在進行一步調試,發現執行finishBeanFactoryInitialization(beanFactory)方法之後,日誌打印了

TestController 創建了

    1

改方法的具體實現如下

protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
        // Initialize conversion service for this context.
        if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
                beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
            beanFactory.setConversionService(
                    beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
        }

        // Register a default embedded value resolver if no bean post-processor
        // (such as a PropertyPlaceholderConfigurer bean) registered any before:
        // at this point, primarily for resolution in annotation attribute values.
        if (!beanFactory.hasEmbeddedValueResolver()) {
            beanFactory.addEmbeddedValueResolver(new StringValueResolver() {
                @Override
                public String resolveStringValue(String strVal) {
                    return getEnvironment().resolvePlaceholders(strVal);
                }
            });
        }

        // Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
        String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
        for (String weaverAwareName : weaverAwareNames) {
            getBean(weaverAwareName);
        }

        // Stop using the temporary ClassLoader for type matching.
        beanFactory.setTempClassLoader(null);

        // Allow for caching all bean definition metadata, not expecting further changes.
        beanFactory.freezeConfiguration();

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

  

通過調試我們可以發現,執行beanFactory.preInstantiateSingletons();這個方法之後,TestController 被創建了,那麼我接下來要做的就是看這個方法具體怎麼實現的,調試過程中,我們通過shift+alt+F7 強制進入代碼,可以看到其具體實現如下:

    @Override
    public void preInstantiateSingletons() throws BeansException {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Pre-instantiating singletons in " + this);
        }

        // Iterate over a copy to allow for init methods which in turn register new bean definitions.
        // While this may not be part of the regular factory bootstrap, it does otherwise work fine.
        List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);

        // Trigger initialization of all non-lazy singleton beans...
        for (String beanName : beanNames) {
            RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
            if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
                if (isFactoryBean(beanName)) {
                    final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
                    boolean isEagerInit;
                    if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
                        isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
                            @Override
                            public Boolean run() {
                                return ((SmartFactoryBean<?>) factory).isEagerInit();
                            }
                        }, getAccessControlContext());
                    }
                    else {
                        isEagerInit = (factory instanceof SmartFactoryBean &&
                                ((SmartFactoryBean<?>) factory).isEagerInit());
                    }
                    if (isEagerInit) {
                        getBean(beanName);
                    }
                }
                else {
                    getBean(beanName);
                }
            }
        }

        // Trigger post-initialization callback for all applicable beans...
        for (String beanName : beanNames) {
            Object singletonInstance = getSingleton(beanName);
            if (singletonInstance instanceof SmartInitializingSingleton) {
                final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
                if (System.getSecurityManager() != null) {
                    AccessController.doPrivileged(new PrivilegedAction<Object>() {
                        @Override
                        public Object run() {
                            smartSingleton.afterSingletonsInstantiated();
                            return null;
                        }
                    }, getAccessControlContext());
                }
                else {
                    smartSingleton.afterSingletonsInstantiated();
                }
            }
        }
    }

  

採用調試的方式,通過觀察我們可以看到,我們的TestController Bean 被加載到beanNames 這個List,通過循環語句最終調用了getBean(beanName);去創建了對象,當然getBean 內部會調用doGetBean() 方法進行對象的獲取或者創建。
總結

    TestController 這個對象的創建,經過了如下方法調用: SpringApplication.run(App.class,args)
    refreshContext(context)
    finishBeanFactoryInitialization(beanFactory)
    beanFactory.preInstantiateSingletons()
    getBean(beanName)
    doGetBean()

    解決這樣一個問題,門檻並非很高,需要的只是一點方法,不過可以由這個問題,牽引出很多問題,比如說:refreshContext 裏面除了創建Bean還做了哪些事情?afterRefresh()又幹了什麼事情?引發一系列的疑問?我想通過這種擴散的方式,可以對Spring 有更加深入的認識,我會在接下來的博客裏面,一步一步通過一個個問題揭開框架的面紗。


原文:https://blog.csdn.net/qq_23980427/article/details/80888040
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章