spring IOC

  一直想閱讀一下spring的源碼,今天終於大體理清了spring IOC容器的初始化整個的脈絡,作個記錄。。。node

  在開源的世界裏,spring無疑是典範級別的,在項目中也是常常使用,因此學習spring優雅的設計對於提高作項目的能力,以及自我能力的提高都是極好的,在網上也看了不少相關的材料,因此就結合spring在web中的運用,逐步分析一下spring IOC容器初始化的整個過程。web

  廢話很少說,上乾貨。。。。spring

  在web項目的web.xml中,咱們已經很是熟悉如何引用spring容器,代碼以下。app

  

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:applicationContext.xml</param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

  因此我學習spring IOC時就以此處爲入口,去查看ContextLoaderListener。該類繼承於ContextLoader,實現了ServletContextListener,ServletContextListener是J2EE中ServletContext生命週期事件監聽接口,因此在web應用啓動後,ContextLoaderListener監聽到相關事件(容器啓動事件),調用contextInitialized()方法,執行初始化,這也是spring IOC接管的開始。ide

  

  下面就具體講講spring如何完成初始化post

  首先咱們看看ContextLoaderListener的contextInitialized()方法, 上代碼學習

/**
     * Initialize the root web application context.
     */
    public void contextInitialized(ServletContextEvent event) {
        this.contextLoader = createContextLoader();
        if (this.contextLoader == null) {
            this.contextLoader = this;
        }
        this.contextLoader.initWebApplicationContext(event.getServletContext());
    }

  該方法中首先初始化了contextLoader,而後調用contextLoader的initWebApplicationContext方法,因此spring IOC容器的初始化工做主要交給了ContextLoaderListener的父類ContextLoader的initWebApplicationContext方法來作,那這個方法具體作了什麼了,請繼續往下看代碼this

  

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
        if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
            throw new IllegalStateException(
                    "Cannot initialize context because there is already a root application context present - " +
                    "check whether you have multiple ContextLoader* definitions in your web.xml!");
        }

        Log logger = LogFactory.getLog(ContextLoader.class);
        servletContext.log("Initializing Spring root WebApplicationContext");
        if (logger.isInfoEnabled()) {
            logger.info("Root WebApplicationContext: initialization started");
        }
        long startTime = System.currentTimeMillis();

        try {
            // Store context in local instance variable, to guarantee that
            // it is available on ServletContext shutdown.
            if (this.context == null) {
                //建立上下文
                this.context = createWebApplicationContext(servletContext);
            }
            if (this.context instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
                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 ->
                        // determine parent for root web application context, if any.
                        ApplicationContext parent = loadParentContext(servletContext);
                        cwac.setParent(parent);
                    }
                    //執行初始化
                    configureAndRefreshWebApplicationContext(cwac, servletContext);
                }
            }
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

            ClassLoader ccl = Thread.currentThread().getContextClassLoader();
            if (ccl == ContextLoader.class.getClassLoader()) {
                currentContext = this.context;
            }
            else if (ccl != null) {
                currentContextPerThread.put(ccl, this.context);
            }

            if (logger.isDebugEnabled()) {
                logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
                        WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
            }
            if (logger.isInfoEnabled()) {
                long elapsedTime = System.currentTimeMillis() - startTime;
                logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
            }

            return this.context;
        }
        catch (RuntimeException ex) {
            logger.error("Context initialization failed", ex);
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
            throw ex;
        }
        catch (Error err) {
            logger.error("Context initialization failed", err);
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
            throw err;
        }
    }

  在此方法中,作了兩件事,如代碼中註釋的兩部分,一、初始化執行環境上下文context , 二、初始化。lua

  下面咱們分別來稍微仔細看下spring如何作這兩件事,首先是建立上下文context,一樣上代碼spa

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
        Class<?> contextClass = determineContextClass(sc);
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
                    "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
        }
        return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
    }
protected Class<?> determineContextClass(ServletContext servletContext) {
        String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
        if (contextClassName != null) {
            try {
                return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
            }
            catch (ClassNotFoundException ex) {
                throw new ApplicationContextException(
                        "Failed to load custom context class [" + contextClassName + "]", ex);
            }
        }
        else {
            contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
            try {
                return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
            }
            catch (ClassNotFoundException ex) {
                throw new ApplicationContextException(
                        "Failed to load default context class [" + contextClassName + "]", ex);
            }
        }
    }

  這兩段代碼就是spring主要用來建立上下文的, 當咱們不曾提供上下文類時,spring經過defaultStrategies加載默認上下文,默認的上下文信息配置在ContextLoader.properties文件中,經過該文件咱們能夠看到,spring默認的上下文環境就是XmlWebApplicationContext的實例,因此spring在默認狀況下經過

ContextLoader.properties文件初始化上下文爲XmlWebApplicationContext的實例。

  spring已經完成了第一件事建立上下文實例,那咱們來看第二件事,也是spring IOC容器初始化過程當中最重要的事,在看以前先上一張XmlWebApplicationContext類的繼承關係圖,便於後面理解spring IOC的初始化過程

  

  話接前面,上面說過spring首先建立了上下文context,爲XmlWebApplicationContext的實例,XmlWebApplicationContext的繼承關係如上圖,下面咱們就來看spring最重要的一件事初始化,首先上前面initWebApplicationContext方法調用的configureAndRefreshWebApplicationContext方法:

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
        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
            String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
            if (idParam != null) {
                wac.setId(idParam);
            }
            else {
                // Generate default id...
                if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
                    // Servlet <= 2.4: resort to name specified in web.xml, if any.
                    wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                            ObjectUtils.getDisplayString(sc.getServletContextName()));
                }
                else {
                    wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                            ObjectUtils.getDisplayString(sc.getContextPath()));
                }
            }
        }

        wac.setServletContext(sc);
     //配置信息 String initParameter
= sc.getInitParameter(CONFIG_LOCATION_PARAM); if (initParameter != null) { wac.setConfigLocation(initParameter); } customizeContext(sc, wac);
     //委託給上下文執行初始化 wac.refresh(); }

  經過這段代碼能夠看出,spring實例化上下文後,開始配置上下文信息,最重要的就是讀取配置文件位置,而後委託給上下文執行初始化,spring執行初始化的過程採用的模版方法進行初始化的,如上面代碼標紅的refresh方法定義在AbstractApplicationContext,經過上面的類繼承關係能夠看到該類。下面咱們來看看該模版方法

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) {
                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

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

                // Propagate exception to caller.
                throw ex;
            }
        }
    }

  該方法定義了spring IOC容器初始化的整個流程,稍微說一句,給方法取個好名字多重要呀,看着spring這些方法取的名字就大概知道這個方法要作什麼事情了,

爲了從宏觀層面去了解spring IOC容器初始化的過程,這裏就不對spring初始化的模版方法裏面的每一個方法進行說明了,只是主要說明一下讀取配置文件中Bean定義文件的解析。

  看到上面代碼中標紅的obtainFreshBeanFactory方法,該方法源碼以下:

/**
     * Tell the subclass to refresh the internal bean factory.
     * @return the fresh BeanFactory instance
     * @see #refreshBeanFactory()
     * @see #getBeanFactory()
     */
    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        refreshBeanFactory();
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        if (logger.isDebugEnabled()) {
            logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
        }
        return beanFactory;
    }

  該方法中調用的refreshBeanFactory方法,refreshBeanFactory方法是一個抽象方法,定義在AbstractApplicationContext類中,而後AbstractRefreshableApplicationContext進行了實現,經過名稱也就知道該方法是作什麼事情的了吧,看下該方法的源碼

@Override
    protected final void refreshBeanFactory() throws BeansException {
        if (hasBeanFactory()) {
            destroyBeans();
            closeBeanFactory();
        }
        try {
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            beanFactory.setSerializationId(getId());
            customizeBeanFactory(beanFactory);
            loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) {
                this.beanFactory = beanFactory;
            }
        }
        catch (IOException ex) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
        }
    }

  在該方法中咱們看到有以下標紅的一句代碼,這也是spring IOC讀取Bean定義的正真入口,在初始化spring IOC容器後,讀取Bean定義文件並解析,看下該方法的源碼

@Override
    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        // Create a new XmlBeanDefinitionReader for the given BeanFactory.
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

        // Configure the bean definition reader with this context's
        // resource loading environment.
        beanDefinitionReader.setEnvironment(this.getEnvironment());
        beanDefinitionReader.setResourceLoader(this);
        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

        // Allow a subclass to provide custom initialization of the reader,
        // then proceed with actually loading the bean definitions.
        initBeanDefinitionReader(beanDefinitionReader);
        loadBeanDefinitions(beanDefinitionReader);
    }

  在該方法中初始化了一個XmlBeanDefinitionReader 讀取類,設置好該類的相關屬性後,經過該類的loadBeanDefinitions方法讀取bean定義,該方法委託給doLoadBeanDefinitions方法,doLoadBeanDefinitions又委託給registerBeanDefinitions方法,而後由該方法進行具體的bean定義讀取,registerBeanDefinitions方法源碼以下

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        documentReader.setEnvironment(this.getEnvironment());
        int countBefore = getRegistry().getBeanDefinitionCount();
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
        return getRegistry().getBeanDefinitionCount() - countBefore;
    }

  在該方法中初始化了一個BeanDefinitionDocumentReader類,該類主要真正負責的讀取bean定義文件,將讀取到的bean定義信息委託給BeanDefinitionParserDelegate去解析,下面先看下初始化BeanDefinitionDocumentReader後,交由BeanDefinitionDocumentReader作了哪些事,看下上圖中標紅方法

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
        this.readerContext = readerContext;
        logger.debug("Loading bean definitions");
        Element root = doc.getDocumentElement();
        doRegisterBeanDefinitions(root);
    }

  能夠看到該方法委託給了doRegisterBeanDefinitions方法來作讀取bean定義文件的信息,接着看doRegisterBeanDefinitions

protected void doRegisterBeanDefinitions(Element root) {
        String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
        if (StringUtils.hasText(profileSpec)) {
            Assert.state(this.environment != null, "Environment must be set for evaluating profiles");
            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                    profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
            if (!this.environment.acceptsProfiles(specifiedProfiles)) {
                return;
            }
        }

        BeanDefinitionParserDelegate parent = this.delegate;
        this.delegate = createHelper(this.readerContext, root, parent);

        preProcessXml(root);
        parseBeanDefinitions(root, this.delegate);
        postProcessXml(root);

        this.delegate = parent;
    }

  該方法經過三個模版方法preProcessXml,parseBeanDefinitions,postProcessXml對配置文件進行處理,其中preProcessXml,postProcessXml爲鉤子方法,咱們能夠在子類重寫這兩個方法進行配置文件預處理與後處理,解析bean定義文件的工做真正由parseBeanDefinitions來完成,查看該方法源碼

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        if (delegate.isDefaultNamespace(root)) {
            NodeList nl = root.getChildNodes();
            for (int i = 0; i < nl.getLength(); i++) {
                Node node = nl.item(i);
                if (node instanceof Element) {
                    Element ele = (Element) node;
                    if (delegate.isDefaultNamespace(ele)) {
                        parseDefaultElement(ele, delegate);
                    }
                    else {
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        }
        else {
            delegate.parseCustomElement(root);
        }
    }

該方法讀取配置文件的每一個結點,而後委託給BeanDefinitionParserDelegate類,來具體進行Bean的定義具體解析,經過遞歸調用首先讀取import引入文件中的bean定義。整個spring讀取bean定義的過程也就到此結束。最後上一下BeanDefinitionParserDelegate類解析bean定義的方法parseBeanDefinitionElement

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
        String id = ele.getAttribute(ID_ATTRIBUTE);
        String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

        List<String> aliases = new ArrayList<String>();
        if (StringUtils.hasLength(nameAttr)) {
            String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
            aliases.addAll(Arrays.asList(nameArr));
        }

        String beanName = id;
        if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
            beanName = aliases.remove(0);
            if (logger.isDebugEnabled()) {
                logger.debug("No XML 'id' specified - using '" + beanName +
                        "' as bean name and " + aliases + " as aliases");
            }
        }

        if (containingBean == null) {
            checkNameUniqueness(beanName, aliases, ele);
        }

        AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
        if (beanDefinition != null) {
            if (!StringUtils.hasText(beanName)) {
                try {
                    if (containingBean != null) {
                        beanName = BeanDefinitionReaderUtils.generateBeanName(
                                beanDefinition, this.readerContext.getRegistry(), true);
                    }
                    else {
                        beanName = this.readerContext.generateBeanName(beanDefinition);
                        // Register an alias for the plain bean class name, if still possible,
                        // if the generator returned the class name plus a suffix.
                        // This is expected for Spring 1.2/2.0 backwards compatibility.
                        String beanClassName = beanDefinition.getBeanClassName();
                        if (beanClassName != null &&
                                beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
                                !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
                            aliases.add(beanClassName);
                        }
                    }
                    if (logger.isDebugEnabled()) {
                        logger.debug("Neither XML 'id' nor 'name' specified - " +
                                "using generated bean name [" + beanName + "]");
                    }
                }
                catch (Exception ex) {
                    error(ex.getMessage(), ele);
                    return null;
                }
            }
            String[] aliasesArray = StringUtils.toStringArray(aliases);
            return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
        }

        return null;
    }

 

總結:

  spring IOC容器初始化過程主要經過BeanDefinitionReader完成Bean定義的解析工做,同時將真正解析bean定義文檔的工做委託給BeanDefinitionDocumentReader來完成,而後BeanDefinitionDocumentReader讀取文件結束後將bean定義註冊工做交由BeanDefinitionParserDelegate來具體完成,BeanDefinitionParserDelegate完成驗證bean定義相關操做後,經過回調容器的註冊接口將全部的Bean定義完成註冊,至此Bean定義解析工做完成,spring IOC容器的初始化工做也將完成。

相關文章
相關標籤/搜索