对spring web启动时IOC源码研究

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了对spring web启动时IOC源码研究相关的知识,希望对你有一定的参考价值。

  1. 研究IOC首先创建一个简单的web项目,在web.xml中我们都会加上这么一句
技术分享
<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>
技术分享

这代表了web容器启动的时候会首先进入ContextLoaderListener这个类,并且之后会去加载classpath下的applicationContext.xml文件。那么重点就在ContextLoaderListener上,点开源码:

技术分享
/**
     * Initialize the root web application context.
     */
    @Override
    public void contextInitialized(ServletContextEvent event) {
        initWebApplicationContext(event.getServletContext());
    }


    /**
     * Close the root web application context.
     */
    @Override
    public void contextDestroyed(ServletContextEvent event) {
        closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
技术分享

里面主要为ServletContextListener接口的两个实现方法。web容器会首先调用contextInitialized方法,传入tomcat封装的容器资源,之后调用父类的初始化容器方法。

技术分享
/**

* The root WebApplicationContext instance that this loader manages.

*/

private WebApplicationContext context;


public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
      ...........省略// 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);
            }
  ......省略
            return this.context;
        
    }
技术分享

这个方法里主要步骤createWebApplicationContext方法用来创建XmlWebApplicationContext这个root根容器,这个容器就是取自servletContextEvent。

loadParentContext方法用来加载父容器。主要方法configureAndRefreshWebApplicationContext用来配置和刷新root容器,在方法内最主要的就是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) {
                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;
            }
        }
    }
技术分享

prepareRefresh方法用来准备之后需要用到的环境。

obtainFreshBeanFactory方法获取beanFactory

技术分享
@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);
        }
    }
技术分享

实际返回的beanFactory为其实现类DefaultListableBeanFactory,实例化该类用来为之后装载xml中实例化的类。

loadBeanDefinitions为重要的方法,用来真正的加载类了,之前的都是准备工作。

技术分享
@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(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);
    }
技术分享

在XmlWebApplicationContext中覆写此方法,内部创建了XmlBeanDefinitionReader类,用来作为xml中bean的读操作器,初始化环境后加载此类

技术分享
    protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
        String[] configLocations = getConfigLocations();
        if (configLocations != null) {
            for (String configLocation : configLocations) {
                reader.loadBeanDefinitions(configLocation);
            }
        }
    }
技术分享

之后获得之前初始化时放入数组的配置文件信息(比如:classpath:applicationContext.xml),用XmlBeanDefinitionReader加载此配置文件。

之后对xml文件信息进行编解码操作:

技术分享
    /**
     * Load bean definitions from the specified XML file.
     * @param resource the resource descriptor for the XML file
     * @return the number of bean definitions found
     * @throws BeanDefinitionStoreException in case of loading or parsing errors
     */
    @Override
    public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        return loadBeanDefinitions(new EncodedResource(resource));
    }
技术分享

关键步骤:

技术分享
InputStream inputStream = encodedResource.getResource().getInputStream();
            try {
                InputSource inputSource = new InputSource(inputStream);
                if (encodedResource.getEncoding() != null) {
                    inputSource.setEncoding(encodedResource.getEncoding());
                }
                return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
            }
            finally {
                inputStream.close();
            }
技术分享

之后正式加载获取到的xml资源解析

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {
            Document doc = doLoadDocument(inputSource, resource);
            return registerBeanDefinitions(doc, resource);
    }

主要步骤是用sax对xml文件解析成Document元素,再注册进beanDefinitions容器中。

doLoadDocument方法的处理方法和sax对xml文档的处理方式就差不多了,具体可以参考sax解析xml流程

解析的过程很多,比如

技术分享
    private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
        if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {//解析import
            importBeanDefinitionResource(ele);
        }
        else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {//解析alias
            processAliasRegistration(ele);
        }
        else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {//解析bean
            processBeanDefinition(ele, delegate);
        }
        else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {//解析beans
            // recurse
            doRegisterBeanDefinitions(ele);
        }
    }
技术分享

将各个标签解析后注入容器。

以上是关于对spring web启动时IOC源码研究的主要内容,如果未能解决你的问题,请参考以下文章

对spring web启动时IOC源码研究

Spring MVC源码 ----- 启动过程与组件初始化

转spring源码解析

spring源码之IOC的核心部分

spring源码之IOC的依赖注入

spring源码分析spring ioc容器之前生今世--DefaultListableBeanFactory源码解读