Spring IOC bean加載過程

我們不要在學習Spring的開始產生畏難情緒。Spring沒有臆想的那麼高深,相反,它幫我們再項目開發中制定項目框架,簡化項目開發。它的主要功能是將項目開發中繁瑣的過程流程化,模式化,使用戶僅在固定文件中增加特定標籤並實現特定邏輯層的代碼就能完成項目開發。下面我們來分析web項目啓動時bean的初始化過程。

我們遵循類的依賴,引用關係來理清spring在這一過程中的架構和細節實現。java web項目入口在web.xml,Spring在此配置入口servlet完成bean的加載。DispatcherServlet 作爲前置控制器是web服務器的入口。

<servlet>  
    <servlet-name>spring</servlet-name>  
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
    <load-on-startup>1</load-on-startup>  </servlet>

我們知道load-on-startup元素標記容器是否在啓動的時候就加載這個servlet(實例化並調用其init()方法)。進入DispatcherServlet尋找init方法,在其父類HttpServletBean中找到。

DispatcherServlet的繼承關係如下圖

init方法細節代碼如下

複製代碼

    /**
     * Map config parameters onto bean properties of this servlet, and
     * invoke subclass initialization.
     * @throws ServletException if bean properties are invalid (or required
     * properties are missing), or if subclass initialization fails.     */
    @Override    public final void init() throws ServletException {        if (logger.isDebugEnabled()) {
            logger.debug("Initializing servlet '" + getServletName() + "'");
        }        // Set bean properties from init parameters.
        try {
            PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
            initBeanWrapper(bw);
            bw.setPropertyValues(pvs, true);
        }        catch (BeansException ex) {            if (logger.isErrorEnabled()) {
                logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
            }            throw ex;
        }        // Let subclasses do whatever initialization they like.        initServletBean();        if (logger.isDebugEnabled()) {
            logger.debug("Servlet '" + getServletName() + "' configured successfully");
        }
    }

複製代碼

其中try catch 模塊做對bean包裝器的初始化相關的工作,initServletBean纔是我們跟蹤的主線調用方法,查看在其父類FrameworkServlet中的此方法

複製代碼

    /**
     * Overridden method of {@link HttpServletBean}, invoked after any bean properties
     * have been set. Creates this servlet's WebApplicationContext.     */
    @Override    protected final void initServletBean() throws ServletException {
        getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");        if (this.logger.isInfoEnabled()) {            this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
        }        long startTime = System.currentTimeMillis();        try {            this.webApplicationContext = initWebApplicationContext();
            initFrameworkServlet();
        }        catch (ServletException ex) {            this.logger.error("Context initialization failed", ex);            throw ex;
        }        catch (RuntimeException ex) {            this.logger.error("Context initialization failed", ex);            throw ex;
        }        if (this.logger.isInfoEnabled()) {            long elapsedTime = System.currentTimeMillis() - startTime;            this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
                    elapsedTime + " ms");
        }
    }

複製代碼

由方法註釋可以瞭解到此方法主要功能爲初始化WebApplicationContext。[Context也就是我們常說的spring容器,打個比方,context就像是一家公司,beans則是公司的工廠,除了工廠,公司還有翻譯,倉庫以及辦公場所等等] .對於ApplicationContext,它提供瞭如下功能:

* Bean factory methods for accessing application components.[獲取應用組件]
* Inherited from {@link org.springframework.beans.factory.ListableBeanFactory}.
* The ability to load file resources in a generic fashion.[加載通用格式資源文件]
* Inherited from the {@link org.springframework.core.io.ResourceLoader} interface.
* The ability to publish events to registered listeners.[發佈事件到監聽器]
* Inherited from the {@link ApplicationEventPublisher} interface.
* The ability to resolve messages, supporting internationalization.[解析信息,支持國際化]
* Inherited from the {@link MessageSource} interface.

它的繼承關係如下,[其實現在我們發現我們的目的最終是查看beanFactory中bean的加載過程]

WebApplicationContext又做了如下擴展

* This interface adds a {@code getServletContext()} method to the generic[增加getServletContext方法]
* ApplicationContext interface, and defines a well-known application attribute name
* that the root context must be bound to in the bootstrap process.

我們回到主線上,進入initWebApplicationContext的方法

複製代碼

    /**
     * Initialize and publish the WebApplicationContext for this servlet.
     * <p>Delegates to {@link #createWebApplicationContext} for actual creation
     * of the context. Can be overridden in subclasses.
     * @return the WebApplicationContext instance
     * @see #FrameworkServlet(WebApplicationContext)
     * @see #setContextClass
     * @see #setContextConfigLocation     */
    protected WebApplicationContext initWebApplicationContext() {
        WebApplicationContext rootContext =
                WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        WebApplicationContext wac = null;        if (this.webApplicationContext != null) {            // A context instance was injected at construction time -> use it
            wac = this.webApplicationContext;            if (wac instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;                if (!cwac.isActive()) {                    // The context has not yet been refreshed -> provide services such as                    // setting the parent context, setting the application context id, etc
                    if (cwac.getParent() == null) {                        // The context instance was injected without an explicit parent -> set                        // the root application context (if any; may be null) as the parent                        cwac.setParent(rootContext);
                    }
                    configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }        if (wac == null) {            // No context instance was injected at construction time -> see if one            // has been registered in the servlet context. If one exists, it is assumed            // that the parent context (if any) has already been set and that the            // user has performed any initialization such as setting the context id
            wac = findWebApplicationContext();
        }        if (wac == null) {            // No context instance is defined for this servlet -> create a local one
            wac = createWebApplicationContext(rootContext);
        }        if (!this.refreshEventReceived) {            // Either the context is not a ConfigurableApplicationContext with refresh            // support or the context injected at construction time had already been            // refreshed -> trigger initial onRefresh manually here.            onRefresh(wac);
        }        if (this.publishContext) {            // Publish the context as a servlet context attribute.
            String attrName = getServletContextAttributeName();
            getServletContext().setAttribute(attrName, wac);            if (this.logger.isDebugEnabled()) {                this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
                        "' as ServletContext attribute with name [" + attrName + "]");
            }
        }        return wac;
    }

複製代碼

第一個if判斷是否在在構造時注入了context實例,在我們的跟蹤場景下不會走這一步;第二個if函數是查找是否在servlet context中註冊了context,依然不會走這步;第三個if函數

會創建WebApplicationContext,我們跟進去看詳細內容

複製代碼

    /**
     * Instantiate the WebApplicationContext for this servlet, either a default
     * {@link org.springframework.web.context.support.XmlWebApplicationContext}
     * or a {@link #setContextClass custom context class}, if set.
     * <p>This implementation expects custom contexts to implement the
     * {@link org.springframework.web.context.ConfigurableWebApplicationContext}
     * interface. Can be overridden in subclasses.
     * <p>Do not forget to register this servlet instance as application listener on the
     * created context (for triggering its {@link #onRefresh callback}, and to call
     * {@link org.springframework.context.ConfigurableApplicationContext#refresh()}
     * before returning the context instance.
     * @param parent the parent ApplicationContext to use, or {@code null} if none
     * @return the WebApplicationContext for this servlet
     * @see org.springframework.web.context.support.XmlWebApplicationContext     */
    protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
        Class<?> contextClass = getContextClass();        if (this.logger.isDebugEnabled()) {            this.logger.debug("Servlet with name '" + getServletName() +
                    "' will try to create custom WebApplicationContext context of class '" +
                    contextClass.getName() + "'" + ", using parent context [" + parent + "]");
        }        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {            throw new ApplicationContextException(                    "Fatal initialization error in servlet with name '" + getServletName() +
                    "': custom WebApplicationContext class [" + contextClass.getName() +
                    "] is not of type ConfigurableWebApplicationContext");
        }
        ConfigurableWebApplicationContext wac =
                (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

        wac.setEnvironment(getEnvironment());
        wac.setParent(parent);
        wac.setConfigLocation(getContextConfigLocation());

        configureAndRefreshWebApplicationContext(wac);        return wac;
    }    protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {        if (ObjectUtils.identityToString(wac).equals(wac.getId())) {            // The application context id is still set to its original default value            // -> assign a more useful id based on available information
            if (this.contextId != null) {
                wac.setId(this.contextId);
            }            else {                // Generate default id...
                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                        ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
            }
        }

        wac.setServletContext(getServletContext());
        wac.setServletConfig(getServletConfig());
        wac.setNamespace(getNamespace());
        wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));        // The wac environment's #initPropertySources will be called in any case when the context        // is refreshed; do it eagerly here to ensure servlet property sources are in place for        // use in any post-processing or initialization that occurs below prior to #refresh
        ConfigurableEnvironment env = wac.getEnvironment();        if (env instanceof ConfigurableWebEnvironment) {
            ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
        }

        postProcessWebApplicationContext(wac);
        applyInitializers(wac);
        wac.refresh();
    }    /**
     * Instantiate the WebApplicationContext for this servlet, either a default
     * {@link org.springframework.web.context.support.XmlWebApplicationContext}
     * or a {@link #setContextClass custom context class}, if set.
     * Delegates to #createWebApplicationContext(ApplicationContext).
     * @param parent the parent WebApplicationContext to use, or {@code null} if none
     * @return the WebApplicationContext for this servlet
     * @see org.springframework.web.context.support.XmlWebApplicationContext
     * @see #createWebApplicationContext(ApplicationContext)     */
    protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {        return createWebApplicationContext((ApplicationContext) parent);
    }

複製代碼

我們可以看到createWebApplicationContext方法設置了Environment、父context、配置文件位置ConfigLocation等操作,查看configureAndRefreshWebApplicationContext邏輯,第一個
if判斷是否可以根據可用信息爲其分配一個更通用的id。接下來的邏輯設置一些屬性,而這些屬性正是我們上文中解釋ApplicationContext時其應擁有的特性
refresh的實現。
首先我們還是來看下AbstractApplicationContext的類繼承關係圖

 這個類實現了context的主要功能,同時他的子類如下

其中

AbstractRefreshableApplicationContext定義了loadBeanDefinitions模板方法,它創建bean factory並load配置文件。它生成了我們熟悉的DefaultListableBeanFactory
AbstractRefreshableConfigApplicationContext增加了設置資源的路徑的能力。
AbstractRefreshableWebApplicationContext增加了針對web的一些特性提供一些context屬性行爲設置能力。

這些context分別會在不同場景下用到,接下來我們仍舊回到主線分析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();
            }            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();
            }
        }
    }

複製代碼

我們一眼發現代碼中對beanFactory初始化設置過程及初始化監聽器,設置message。進入初始化非懶加載bean的方法finishBeanFactoryInitialization

 

複製代碼

    /**
     * Finish the initialization of this context's bean factory,
     * initializing all remaining singleton beans.     */
    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();
    }

複製代碼

 這個方法負責了context的beanFactory中單例bean的初始化。


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