Spring&WEB整合原理及源码分析

1. 概述

表现层和业务层整合:
1. Jsp/Servlet整合Spring;
2. Spring MVC整合SPring;
3. Struts2整合Spring;
本文主要介绍Jsp/Servlet整合Spring原理及源码分析。

2. 整合过程

Spring&WEB整合,主要介绍的是Jsp/Servlet容器和Spring整合的过程,当然,这个过程是Spring MVC或Strugs2整合Spring的基础。
Spring和Jsp/Servlet整合操作很简单,使用也很简单,按部就班花不到2分钟就搞定了,本节只讲操作不讲原理,更多细节、原理及源码分析后续过程陆续涉及。
(1)导入必须的jar包,本例spring-web-x.x.x.RELEASE.jar;
(2)配置web.xml,本例示例如下;

<?xml version="1.0" encoding="UTF-8"?>  
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">  
  <display-name>spring</display-name>  
  <context-param>  
    <param-name>contextConfigLocation</param-name>  
    <param-value>applicationContext.xml</param-value>  
  </context-param>  
  <listener>  
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
  </listener>  

  <welcome-file-list>  
    <welcome-file>index.jsp</welcome-file>  
  </welcome-file-list>  
</web-app>  

只需要配置context-param和listener就够了。配置项的内容含义:ServletContext.setAttribute(“contextConfigLocation”, “applicationCOntext.xml”),而中配置的侦听器则来自于第一步中导入的spring-web-x.x.x.RELEASE.jar包。
就这么两步,Spring和Jsp/Servlet就已经整合好了,验证一下,启动tomcat不报错就可以了。

3. 整合原理

原理:让Spring容器随着tomcat容器ServletContext的启动而启动,并且在初始化完成后放到整个应用都可以访问的范围。
回想一下,在非web环境下,使用Spring是需要我们手动把ApplicationContext对象创建出来使用。在web环境下,使用Spring也是需要把ApplicationContext对象创建出来,不过这个步骤大可交给服务器来做,你不必自己再去写个单例类,web环境中单例对象多了去了,servlet是单例的,filter是单例的,listener也是单例,这三个,随便找一个在初始化的时候把ApplicationContext对象创建出来,然后放到整个应用都可以访问的ServletContext容器中就可以了。
总结一下:
(1)让ApplicationContext随着服务器的启动而启动,可以借助与Servlet/Filter/Listener任何一个;
(2)把创建好的ApplicationContext放到ServletContext中,整个应用范围,想怎访问就怎么访问;
整合原理就这么多,你自己分分钟都可以整一个出来,至于代码健不健壮再说,先用再调嘛。但是大可不必这么麻烦,Spring已经把这一切做好了,你只要拿过来用就可以了。
回顾一下第一节的内容,导入一个jar包spring-web-x.x.x.RELEASE.jar,配置了一个侦听器listener,没错,上面的原理都被这个jar包中的这个侦听器给实现了。早期的Spring整合web,支持servlet和listener,不过在Spring 3.x的时候,Spring官方访问已经明确不支持Servlet方式并且已经将相关源码移出web的jar包,所以现在Spring整合WEB,就一条路,listener。
有了ApplicationContext对象,IOC/DI、AOP、Spring JDBC以及事务、国际化ResourceMessage、Spring事件机制、FactoryBean、Spring JNDI等等,想怎么用怎么用。
再分析源码之前,介绍一个工具,org.springframework.web.context.support.WebApplicationContextUtils,这是Spring官方提供的一个web环境下便捷从ServletContext中获取ApplicationContext的工具类,使用示例如下所示。

WebApplicationContext applicationContext = WebApplicationContextUtils. getWebApplicationContext(this.getServletContext()); 

你不用再去记忆ApplicationContext放入ServletContext中冗长的key,这个方法的源码也很简单,不过Spring写的又臭又长,原理如下所示。

/** * String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = * WebApplicationContext.class.getName() + ".ROOT"; */  
WebApplicationContext context = (WebApplicationContext) this.getServletContext()  
                .getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);  

好了,有了以上的铺垫,并且你对Spring熟悉,那么在web环境下使用Spring对你来说已经毫无压力。

4. 源码分析

4.1 contextInitialized

ServletContextListener侦听器是Jsp/Servlet规范中八大侦听器之一,用来监听ServletContext的创建和销毁。第一节中配置的侦听器ContextLoaderListener就实现了该接口。

package org.springframework.web.context;  

import javax.servlet.ServletContextEvent;  
import javax.servlet.ServletContextListener;  

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {  
    public ContextLoaderListener() {}  

    public ContextLoaderListener(WebApplicationContext context) {  
        super(context);  
    }  
    /** * 该方法实现与ServletContextListener接口 * 当ServletContext被创建之后,服务器会自动调用该方法 * 而该方法就是Spring整合WEB的入口 */  
    public void contextInitialized(ServletContextEvent event) {  
        initWebApplicationContext(event.getServletContext());  
    }  
    /** * 该方法实现与ServletContextListener接口 * 当ServletContext销毁的时候,服务器会自动调用该方法 * 最后一节详解该方法 */  
    public void contextDestroyed(ServletContextEvent event) {  
        closeWebApplicationContext(event.getServletContext());  
        ContextCleanupListener.cleanupAttributes(event.getServletContext());  
    }  
} 

随着服务器的启动,ContextLoaderListener侦听器对象会被实例化并且调用contextInitialized(ServletContextEvent event)方法,再在该方法中调用包裹了初始化所有逻辑的initWebApplicationContext(event.getServletContext())方法,深入一下这个方法.

4.1.1 initWebApplicationContext

这个方法可大发了,ApplicationContext就在这里被创建完成,并且调用refresh()方法。refresh()方法对于Spring有多重要我就不说了,BeanFactory的创建以及配置文件applicationContext加载解析成BeanDefinition以及单例Bean的实例化和缓存、完成国际化机制的初始化、完成Spring事件机制的初始化、发布Spring容器刷新事件等等,这个方法成功调用返回标志着Spring容器启动成功,可以对外提供服务。
简单总结一下该方法的内容:
(1)创建WebApplicationContext对象;
(2)将配置的contextConfigLocation参数(即Spring配置文件applicationContext.xml)传入ApplicationContext对象并调用refresh()方法刷新Spring容器,完成Spring容器的创建和启动;
(3)将创建好的ApplicationContext放入ServletContext中;
(4)将ApplicationContext和当前线程的类加载器作为KV存入到ContextLoaderListener中;

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {  
    //如果ServletContext中已经放了一个ApplicationContext,抛异常 
    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 {  
        //1. 创建WebApplicationContext,这是一个很重要的方法,可以控制初始化的ApplicationContext类型,下文详解 
        if (this.context == null) {  
            this.context = createWebApplicationContext(servletContext);  
        }  
        //将上文创建的WebApplicationContext强转成子类ConfigurableWebApplicationContext 
        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);  
                }  
                //2. 整个Spring整合Web的灵魂所在,在里面调用了refresh()方法,完成Spring容器的启动,下文详解详解详解 
                configureAndRefreshWebApplicationContext(cwac, servletContext);  
            }  
        }  
        //3. 将ApplicationContext放入ServletContext中 
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);  

        //4. 将ApplicationContext根据类加载器放入ContextLoaderListener的ConcurrentHashMap的逻辑 
        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");  
        }  
        //refresh()方法调用完成,标志着Spring对外可以提供服务 
        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;  
    }  
}  

上面都是些包来包去的方法,下面才是精华。上面错过就算了,下面错过就亏了。

4.1.1.1 createWebApplicationContext

在调用createWebApplicationContext(servletContext)方法的时候,会判断ContextLoaderListener的this.context是否为null,不为null才创建新的对象,之所以这样判断,是因为ContextLoaderListener的构造方法有两种,含参和不含参,另外,如果不是在启动服务时触发refresh方法,而是在运行中手动触发refresh方法的话,就不必在重新创建ApplicationContext对象,而只需要把这个对象后面的逻辑刷新一下就可以了。

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {  
    //1. 获得ApplicationContext类型的字节码,这里可以自定义ApplicationContext的类型 
    //默认是XmlApplicationContext 
    //在web.xml中通过设置参数,你可以在web中使用Spring 3.0的AnnotationConfigWebApplicationContext 
    //或者是其他任意类型的ApplicationContext 
    Class<?> contextClass = determineContextClass(sc);  
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {// 这个判断决定了自定义ApplicationContext的范围,下文会有一个自定义的举例 
        throw new ApplicationContextException("Custom context class [" + contextClass.getName() +  
                "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");  
    }  
    //2. 利用反射来实例化ApplicationContext 
    //这里的逻辑是通过反射获取默认构造函数,然后newInstance(); 
    return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);  
}  

determineContextClass

// 默认WebApplicationContext初始化对象的配置文件 
private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";  
// 配置文件工具 
private static final Properties defaultStrategies;  

protected Class<?> determineContextClass(ServletContext servletContext) {  
    // 从web.xml中获取配置的ApplicationContext类的全量路径 
    // public static final String CONTEXT_CLASS_PARAM = "contextClass"; 
    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 {  
        // 如果没有配置contextClass,那么默认就创建XmlWebApplicationContext 
        // 这个参数在spring-web-x.x.x.RELEASE.jar的配置文件中 
        // /org/springframework/web/context/ContextLoader.properties 
        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);  
        }  
    }  
}  

先看ContextLoader.properties配置文件中的内容。

# Default WebApplicationContext implementation class for ContextLoader.
# Used as fallback when no explicit context implementation has been specified as context-param.
# Not meant to be customized by application developers.
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

如果没有在web.xml中没有配置contextClass,那么默认加载的就是XmlWebApplicationContext,如果配置了,那么就加载配置的ApplicationContext类型,但是有一个条件,即该类必须是ConfigurableWebApplicationContext或其子类。一个简单的配置例子,web容器启动的时候,加载AnnotationConfigWebApplicationContext类。

<?xml version="1.0" encoding="UTF-8"?>  
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">  
  <display-name>spring</display-name>  

  <context-param>  
    <param-name>contextClass</param-name>  
    <param-value>org.springframework.context.annotation.AnnotationConfigWebApplicationContext</param-value>  
  </context-param>  

  <context-param>  
    <param-name>contextConfigLocation</param-name>  
    <!-- 这里不再加载applicationContext.xml,而是加载自定义的配置类 -->  
    <param-value>cn.wxy.configuration.AnnoBeanConfiguation</param-value>  
  </context-param>  

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

  <welcome-file-list>  
    <welcome-file>index.jsp</welcome-file>  
  </welcome-file-list>  
</web-app>  

BeanUtils.instantiateClass(contextClass)
在成功加载ApplicationContext的字节码并获得Class对象之后,利用反射获取ApplicationContext实例,源码很简单,核心就就一句话clazz.getDeclaredConstructor().newInstance(),反射获取默认构造器,然后实例化。

    public static <T> T instantiateClass(Class<T> clazz) throws BeanInstantiationException {
        Assert.notNull(clazz, "Class must not be null");
        if (clazz.isInterface()) {
            throw new BeanInstantiationException(clazz, "Specified class is an interface");
        }
        try {
            Constructor<T> ctor = (KotlinDetector.isKotlinType(clazz) ?
                    KotlinDelegate.getPrimaryConstructor(clazz) : clazz.getDeclaredConstructor());
            return instantiateClass(ctor);
        }
        catch (NoSuchMethodException ex) {
            throw new BeanInstantiationException(clazz, "No default constructor found", ex);
        }
        catch (LinkageError err) {
            throw new BeanInstantiationException(clazz, "Unresolvable class definition", err);
        }
    }

4.1.1.2 configureAndRefreshWebApplicationContext

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {  
    // 从web.xml中获取context-param配置参数contextId,没有就采用默认 
    // contextId这个不知道用来干什么,不涉及主要逻辑,此处不予关注 
    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... 
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +  
                    ObjectUtils.getDisplayString(sc.getContextPath()));  
        }  
    }  
    // 从context-param中获取Spring配置文件applicationContext.xml路径 
    // 参数contextConfigLocation为第一节中web.xml中配置的参数 
    wac.setServletContext(sc);  
    String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);  
    if (configLocationParam != null) {  
        wac.setConfigLocation(configLocationParam);  
    }  
    //初始化web环境 
    // 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(sc, null);  
    }  
    customizeContext(sc, wac);  
    // 刷新Spring容器 
    // 该方法调用完成,标志着Spring容器可以对外提供服务 
    wac.refresh();  
}  

这个方法主要是将Spring配置文件路径关联到ApplicationContext,并没有进行解析,解析过程在refresh中获取BeanFactory的时候完成.
最重要的方法就是refresh()方法,它包含了Spring所有的启动逻辑,这里只是简单的列了一下,并没有深入,深入就太多了,写不完,可以参看《Spring:源码解读Spring IOC原理》

public void refresh() throws BeansException, IllegalStateException {    
   synchronized (this.startupShutdownMonitor) {    
       //调用容器准备刷新的方法,获取容器的当时时间,同时给容器设置同步标识 
       prepareRefresh();    
       //告诉子类启动refreshBeanFactory()方法,Bean定义资源文件的载入从 
      //子类的refreshBeanFactory()方法启动 
       ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();    
       //为BeanFactory配置容器特性,例如类加载器、事件处理器等 
       prepareBeanFactory(beanFactory);    
       try {    
           //为容器的某些子类指定特殊的BeanPost事件处理器 
           postProcessBeanFactory(beanFactory);    
           //调用所有注册的BeanFactoryPostProcessor的Bean 
           invokeBeanFactoryPostProcessors(beanFactory);    
           //为BeanFactory注册BeanPost事件处理器. 
           //BeanPostProcessor是Bean后置处理器,用于监听容器触发的事件 
           registerBeanPostProcessors(beanFactory);    
           //初始化信息源,和国际化相关. 
           initMessageSource();    
           //初始化容器事件传播器. 
           initApplicationEventMulticaster();    
           //调用子类的某些特殊Bean初始化方法 
           onRefresh();    
           //为事件传播器注册事件监听器. 
           registerListeners();    
           //初始化所有剩余的单态Bean. 
           finishBeanFactoryInitialization(beanFactory);    
           //初始化容器的生命周期事件处理器,并发布容器的生命周期事件 
           finishRefresh();    
       }    
       catch (BeansException ex) {    
           //销毁以创建的单态Bean 
           destroyBeans();    
           //取消refresh操作,重置容器的同步标识. 
           cancelRefresh(ex);    
           throw ex;    
       }    
   }    
}  

4.1.1.3 收尾工作

(1)接下来将创建好的XmlWebApplicationContext放入ServletContext中;
(2)以当前线程的类加载器为key,XmlWebApplicationContext为value,放入ContextLoaderListener的HashMap中;
(3) 将XmlApplicationContext和ContextLoaderListener的this.currentContext绑定;

4.2 contextDestroyed

public void contextDestroyed(ServletContextEvent event) {  
    // 关闭WebApplicationContext 
    closeWebApplicationContext(event.getServletContext());  
    // 将ServletContext中Spring相关的对象清理 
    ContextCleanupListener.cleanupAttributes(event.getServletContext());  
}  

清理逻辑就很简单了,源码简单注释如下。

public void closeWebApplicationContext(ServletContext servletContext) {  
    servletContext.log("Closing Spring root WebApplicationContext");  
    try {  
        // 关闭Spring容器,此操作会调用Bean生命周期中destory-method和注解preDestory清理资源:销毁单例bean、销毁BeanFacotry,删除JVM关闭钩子; 
        if (this.context instanceof ConfigurableWebApplicationContext) {  
            ((ConfigurableWebApplicationContext) this.context).close();  
        }  
    } finally {  
        // 清理ContextLoaderListener 
        ClassLoader ccl = Thread.currentThread().getContextClassLoader();  
        if (ccl == ContextLoader.class.getClassLoader()) {  
            currentContext = null;  
        } else if (ccl != null) {  
            currentContextPerThread.remove(ccl);  
        }  
        // 清理放入ServletContext中的ApplicationContext 
        servletContext.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);  
        if (this.parentContextRef != null) {  
            this.parentContextRef.release();  
        }  
    }  
}  
// 把Servlet中Spring相关的内容清掉 
static void cleanupAttributes(ServletContext sc) {  
    Enumeration<String> attrNames = sc.getAttributeNames();  
    while (attrNames.hasMoreElements()) {  
        String attrName = attrNames.nextElement();  
        if (attrName.startsWith("org.springframework.")) {  
            Object attrValue = sc.getAttribute(attrName);  
            if (attrValue instanceof DisposableBean) {  
                try {  
                    ((DisposableBean) attrValue).destroy();  
                }  
                catch (Throwable ex) {  
                    logger.error("Couldn't invoke destroy method of attribute with name '" + attrName + "'", ex);  
                }  
            }  
        }  
    }  
}  
    原文作者:Spring MVC
    原文地址: https://blog.csdn.net/uftjtt/article/details/80351102
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞