Spring 初始化过程

借着找工作的契机,作为Java web开发中经常用到和考到的Spring,简单翻了翻源码,熟悉了一下它的加载过程。

先上一张类图:



这里借用在web项目中的加载过程来熟悉Spring ApplicationContext的加载过程:


1. 在一般的Web项目中,我们很多情况下是利用org.springframework.web.context.ContextLoaderListener这个类进行容器的初始化。该类会被Web容器(如Tomcat)自动实例化,并调用contextInitialized方法。

 

    /**

    * Initialize the root web application context.

     */

    @Override

    public void contextInitialized(ServletContextEvent event) {

        initWebApplicationContext(event.getServletContext());

    }

 

2. initWebApplicationContext方式是从父类ContextLoader中继承来的。该方法的大致的逻辑是:判定web容器中是否注册了ROOT_APPLICATION_CONTEXT_STTRIBUTE(为WebApplicationContext.class.getName()+ “.ROOT”)的属性,如果有,则抛出异常,以此保证一个Web容器中只有一个Spring根容器;创建容器的时候,要判定需要实例化哪种类来实例化当前web容器的Spring根容器,如果我们设置了名称为“contextClass”的context-param,则取我们设置的类,该类应当实现ConfigurableWebApplicationContext接口或继承自实现了该接口的子类(如XmlWebApplicationContext、GroovyWebApplicationContext和AnnotationConfigWebApplicationContext),通常我们都不会设置,Spring会默认取与ContextLoader同目录下的ContextLoader.properties中记录的类名作为根容器的类型(默认是org.springframework.web.context.support.XmlWebApplicationContext);实例化容器;配置容器;设置容器为Web容器的属性。下面代码中去掉了log和异常等部分。

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!");

        }

            if (this.context == null) {

                this.context = createWebApplicationContext(servletContext);

            }

            if (this.context instanceof ConfigurableWebApplicationContext) {

                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;

                if (!cwac.isActive()) {

                    if (cwac.getParent() == null) {

                        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;

    }

 

3. configureAndRefreshWebApplicationContext方法负责对容器进行初始化,该方法的逻辑主要有一下几点:设置一个contextId(从contextId这个param获取,如果没有则默认是WebApplicationContext的类名 + “:” + servlet context的路径);设置配置位置(从contextConfigLocation 这个param获取,如果未配置,则默认是/WEB-INF/applicationContext.xml,在XmlWebApplicationContext中可以看出);自定义该congtext;调用该Context的refresh()方法。

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {

        if (ObjectUtils.identityToString(wac).equals(wac.getId())) {

            String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);

            if (idParam != null) {

                wac.setId(idParam);

            }

            else {

                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +

                        ObjectUtils.getDisplayString(sc.getContextPath()));

            }

        }

 

        wac.setServletContext(sc);

        String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);

        if (configLocationParam != null) {

            wac.setConfigLocation(configLocationParam);

        }

        ConfigurableEnvironment env = wac.getEnvironment();

        if (env instanceof ConfigurableWebEnvironment) {

            ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);

        }

        customizeContext(sc, wac);

        wac.refresh();

    }

 

4. ConfigurableApplicationContext接口的实现类AbstractApplicationContext中的refresh方法,定义了一个模版,在该方法里,会完成加载资源、配置文件解析、Bean定义的注册、组件的初始化等工作。每一步工作都定义在响应的方法中,清晰明了。下面的方法省略了异常处理。

 

synchronized (this.startupShutdownMonitor) {.

            prepareRefresh(); //准备

            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();//获得一个Bean工厂

            prepareBeanFactory(beanFactory); //准备好工厂

                postProcessBeanFactory(beanFactory);//处理工厂时执行

                invokeBeanFactoryPostProcessors(beanFactory);//执行工厂处理

                registerBeanPostProcessors(beanFactory);//注册bean产生拦截器

                initMessageSource();//初始化消息源

                initApplicationEventMulticaster();//初始化应用事件广播器

                onRefresh();//初始化该容器子类其他特定的bean

                registerListeners();//检查监听器并注册

                finishBeanFactoryInitialization(beanFactory);// 初始化非lazy的singleton的bean

 

                finishRefresh();//发布结束fresh消息

}

 

5. 这几个方法中比较重要的方法是obtainFreshBeanFactory方法和finishBeanFactoryInitialization方法,一个用来获得bean工厂,一个用来实例化bean并注入的。这里先分析obtainFreshBeanFactory方法。在继承链中,AbstractApplicationContext实现了该方法,并定义了refreshBeanFactory方法让后代实现。AbstractRefreshableApplicationContext又实现了refreshBeanFactory方法,如果有一个BeanFactory销毁它,然后再创建一个。

 

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;

            }

        }

    }

 

6. loadBeanDefinitions方法会由子类实现,在XmlWebApplicationContext中就实现了该方法。该方法的逻辑为:new一个XmlBeanDefinitionReader;设置该Reader的ResourceLoader(XmlWebApplicationContext设置的是它自己,因为它间接实现了ResourceLoader接口);、使用该Reader加载Bean定义。

 

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {

        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

        beanDefinitionReader.setEnvironment(getEnvironment());

        beanDefinitionReader.setResourceLoader(this);

        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

 

        initBeanDefinitionReader(beanDefinitionReader);

        loadBeanDefinitions(beanDefinitionReader);

    }

 

7. loadeBeanDefinitions方法会使用reader从每个配置中读取bean定义。该方法大概逻辑为:从每个location解析出一些resouce(代表特定的资源,如一个文件)。在AbstractBeanDefinitionReader中实现的loadBeanDefinitions(String,Set<Resource> actualResources) 方法如下:

 

public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {

        ResourceLoader resourceLoader = getResourceLoader();

        if (resourceLoader == null) {

            throw new BeanDefinitionStoreException(

                    "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");

        }

 

        if (resourceLoader instanceof ResourcePatternResolver) {

            try {

                Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);

                int loadCount = loadBeanDefinitions(resources);

                if (actualResources != null) {

                    for (Resource resource : resources) {

                        actualResources.add(resource);

                    }

                }

                if (logger.isDebugEnabled()) {

                    logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");

                }

                return loadCount;

            }

            catch (IOException ex) {

                throw new BeanDefinitionStoreException(

                        "Could not resolve bean definition resource pattern [" + location + "]", ex);

            }

        }

        else {

            Resource resource = resourceLoader.getResource(location);

            int loadCount = loadBeanDefinitions(resource);

            if (actualResources != null) {

                actualResources.add(resource);

            }

            if (logger.isDebugEnabled()) {

                logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");

            }

            return loadCount;

        }

    }

 

8. 在XmlBeanDefinitionReader中才是真正的加载Bean定义的实现。它从每个Resource中获得输入流,封装成InputSource;调用doLoadBeanDefinitions从该InputSource中获得一个Document,并使用registerBeanDefinitions方法根据该Document注册BeanDefinitions;registerBeanDefinitions方法中先是创建一个DefaultBeanDefinitionDocumentReader实例,再使用该实例来注册BeanDefinition;该实例会从Document的根元素开始注册BeanDefinition。值得注意的是在该类又会委派一个BeanDefinitionParserDelegate来解析Document元素。

 

    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);

        }

    }

 

9. BeanDefinitionParserDelegate会根据节点的命名空间使用不同的NamespaceHandler进行解析。在XmlBeanDefinitionReader创建DefaultBeanDefinitionDocumentReader时候会传入一个XmlReaderContext,该Context中保存了一个NamespaceHandlerResolver,该HandlerResolver维护一个Map,用来解析对于一个命名空间的具体Handler,其默认使用META-INF/spring.handlers 文件保存Handler的种类,Spring支持的Handler如下图:

 

 

BeanDefinitionDelegate使用获得的NamespaceHandler解析元素节点,获得BeanDefinition。

10. 不同的NamespaceHandler解析过程不一样。同时,一个NamespaceHandler中有多个Parser来解析不同种类的元素。以ContextNamspaceHandler为例,它支持的Parser如下图所示:

 



  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值