Spring源码深度解析(郝佳)-学习-源码解析-Spring MVC(一)

Spring源码 专栏收录该内容
49 篇文章 3 订阅

        Spring框架提供了构建Web应用程序的全部功能MVC模块,通过策略接口,Spring框架是高度可配置的,而且支持多种视图技术,例如JavaServer Pages(JSP),Velocity,Tiles,IText和POI,Spring MVC框架并不知道使用的视图,所以不会强迫您只使用JSP技术,Spring MVC分离了控制器,模型对象,分派器以及处理程序对象的角色,这种分离让它们更容易进行定制。
        Spring的MVC是基于Servlet功能实现的,通过实现Servlet接口的DispatcherServlet来封装其核心功能实现,通过将请求分派给处理程序,同时带有可配置的处理程序映射,视图解析,本地语言,主题解析以及上传下载文件支持,默认的处理程序是非常简单的Controller接口,只有一个方法ModelAndView HandleRequest(request,response),Spring提供了一个控制器层次的结构,可以派生子类,如果应用程序需要处理用户输入表单,那么可以继承AbstractFormController。如果需要把多页输入处理一个表单,那么可以继承AbstractWizardFormController。
        对SpringMVC或者其他比较成熟的MVC框架而言,解析问题不外乎以下几点。

  • 将Web页面的请求传给服务器。
  • 根据不同的请求处理不同的逻辑单元。
  • 返回处理结果数据并跳转至响应页面。

我们首先通过一个简单的示例来看看Spring MVC的使用

  1. 配置web.xml

        一个Web中可以没有web.xml文件,也就是说。web.xml文件并不是Web工程必需的,web.xml文件用来初始化配置信息,比如Welcome页面,servlet,servlet-mapping,filter,listener,启动加载级别等,但是Spring MVC 的实现原理是通过servlet拦截所有的URL来达到控制的目的。所以web.xml的配置是必需的。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee" 
         xmlns:web="http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee">
    <display-name>spring_tiny</display-name>

    <!--使用ContextLoaderListener配置时,需要告诉它Spring配置文件的位置-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring_1_100/config_71_80/spring75_mvc/spring75.xml</param-value>
    </context-param>
    
    <!--Spring MVC的前端控制器-->
    <!--当DispatcherServlet载入后,它将从一个XML文件中载入Spring的应用上下文,该XML文件的名字取决于<servlet-name></servlet-name>-->
    <!--这里DispatcherServlet将试图从一个叫做Spring-servlet.xml的文件中载入应用上下文,其默认位于WEB-INF目录下-->
    <servlet>
        <servlet-name>spring_tiny</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>


    <servlet-mapping>
        <servlet-name>spring_tiny</servlet-name>
        <url-pattern>*.htm</url-pattern>
    </servlet-mapping>
    
    <!--配置上下文载入器-->
    <!--上下文载入器载入除DispatcherServlet载入的配置文件之外 的其他上下文配置文件-->
    <!--最常用的上下文载入器是一个Servlet监听器,茂名称为ContextLoaderListener-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <listener>
        <listener-class>com.spring_1_100.test_71_80.test75_springmvc.MyDataContextListener</listener-class>
    </listener>

    
    <!-- 自己配置描述文件,需要多少个描述文件就配置多少 -->
    <jsp-config>
        //<!-- 配置c描述文件-对应c标签,这里的taglib-uri对应jsp中引入的uri -->
        <taglib>
            <taglib-uri>http://www.codecoord.com</taglib-uri>
            <taglib-location>/WEB-INF/c.tld</taglib-location>
        </taglib>
    </jsp-config>

</web-app>

        Spring的MVC之所以必需要配置web.xml,其实最关键的是要配置两个地方。

  • contextConfigLocation:Spring的核心就是配置文件,可以说配置文件是Spring中必不可少的东西,而这个参数就是使Web与Spring的配置文件相结合的一个关键配置。
  • DispatcherServlet:包含了Spring MVC的请求逻辑,Spring 使用此类拦截Web请求并进行相应的逻辑处理。
2. 创建Spring配置文件spring75.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>

    <bean id="exceptionHandler" class="com.spring_1_100.test_71_80.test75_springmvc.MyExceptionHandler"></bean>
    
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/list"/>
            <bean class="com.spring_1_100.test_71_80.test75_springmvc.MyTestInterceptor"></bean>
        </mvc:interceptor>
    </mvc:interceptors>

</beans>

        InternalResourceViewResolver
是一个辅助bean,会在ModelAndView返回的视图名前加上prefix指定的前缀,再在最后加上suffix指定的后缀,例如:由于XXController返回的是ModelAndView中的视图名是testview,故该视图解析器将在/WEB-INF/jsp/testview.jsp处查找视图。

3. 创建model

        模型对于Spring MVC 来说并不是必不可少的,如果处理程序非常简单,完全可以忽略,模型创建寺主要的目的就是承载数据,使用传输数据更加方便。

public class User {
    private String username;
    private Integer age;


    public User(String username, Integer age) {
        this.username = username;
        this.age = age;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}
4.创建controller

        控制器用于处理Web请求,每个控制器都对应一个逻辑处理。

public class UserController extends AbstractController {


    @Override
    protected ModelAndView handleRequestInternal(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws Exception {
        List<User> userList = new ArrayList<User>();
        User userA = new User("zhangsan",27);
        User userB = new User("lisi",28);
        userList.add(userA);
        userList.add(userB);
        String myData =(String) getServletContext().getAttribute("myData");

        System.out.println("===========" + myData);
        int i = 1 ;
        int j = 0 ;
        //int c = i / j ;

        return new ModelAndView("userlist","users",userList);
    }
}

        在请求的最后返回了ModelAndView类型的实例,ModelAndView类在Spring MVC 中占有很重要的地位,控制器执行方法都必需返回一个ModelAndView,ModelAndView对象保存了视图及视图显示的模型数据,例如其中的参数如下:

  • 第一个参数userList:视图组件的逻辑名称,这里视图的逻辑名称就是userList,视图解析器会使用该名称查找实际的View对象。
  • 第二个参数user:传递给视图模型的对象名称。
  • 第三个参数userList:传递给视图模型的对象的值。
5.创建视图文件userList.jsp
<%@ page language="java" pageEncoding="utf-8" %>
<%@ taglib prefix="c" uri="http://www.codecoord.com" %>
<h2>imya</h2>

<c:forEach items="${users}" var="user">
    <c:out value="${user.username}"></c:out><br/>
    <c:out value="${user.age}"></c:out><br/>
</c:forEach>

        视图文件中用于展现请求处理结果,通过对JSTL的支持,可以很方便的展现在控制器中放入的ModelAndView中的处理结果数据。

6.创建Servlet配置文件Spring-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="simpleUrlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                <prop key="/userlist.htm">userController</prop>
                <prop key="/helloWorldLastModified.htm">helloWorldLastModifiedCacheController</prop>
            </props>
        </property>
    </bean>

    <bean id="userController" class="com.spring_1_100.test_71_80.test75_springmvc.UserController"></bean>

    <bean name="helloWorldLastModifiedCacheController" class="com.spring_1_100.test_71_80.test75_springmvc.HelloWorldLastModifiedCacheController"></bean>

</beans>
7.添加自定义拦截器
public class MyTestInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        long startTime = System.currentTimeMillis();
        request.setAttribute("startTime",startTime);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        long startTime = (long)request.getAttribute("startTime");
        request.removeAttribute("startTime");
        long endTime = System.currentTimeMillis();
        System.out.println("request time :" + (endTime -startTime));
        modelAndView.addObject("handlingTime",endTime-startTime);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}
8.添加自定义异常处理器
public class MyExceptionHandler implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        request.setAttribute("exception", ex.toString());
        request.setAttribute("exceptionStack", ex);
        return new ModelAndView("error/exception");
    }
}
9.添加自定义监听器
public class MyDataContextListener implements ServletContextListener {


    private ServletContext context;

    // 该方法在 ServletContext 启动后被调用,并准备好处理客户端请求
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        this.context = sce.getServletContext();
        //通过这里可以实现自己的逻辑并将结果记录在属性中
        context.setAttribute("myData","this is my Data");
    }


    //这个方法在ServletContext将要关闭的时候调用
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        this.context = null;
    }
}
10.添加缓存Handler
public class HelloWorldLastModifiedCacheController extends AbstractController implements LastModified {
    private long lastModified;

    @Override
    protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
        System.out.println(" cache modified 请求");
        response.getWriter().write("this");
        return null;
    }

    @Override
    public long getLastModified(HttpServletRequest request) {
        if(lastModified == 0l){
            // 第一次或者逻辑变化的时候,应该重新的返回内容最新的修改时间戳
            lastModified = System.currentTimeMillis();
        }
        return lastModified;
    }
}

        因为Spring MVC 是基于Servlet的实现,所以在Web启动的时候,服务器会首先尝试加载对应的Servlet中的配置文件,而为了让项目更加模块化,通常我们将Web部分的配置都存放于此配置文件中。
        至此,己经完成了Spring MVC的搭建,启动服务器。
        idea中的如下配置:
在这里插入图片描述
在这里插入图片描述

输入http://localhost:8080/userlist.htm
看到了服务器返回界面,如下图所示:
在这里插入图片描述

ContextLoaderListener

        对于SpringMVC功能实现分析,我们首先从web.xml开始,在web.xml文件中我们首先配置就是在ContextLoadListener,那么它所提供的功能有哪些呢?又是如何实现的呢?
        当使用编程的方式的时候,我们可以直接将Spring配置信息作为参数传入到Spring容器中,如
        ApplicationContext context = new ClassPathXmlApplicationContext(“applicationContext.xml”);
        但是在Web下,我们需要更多的是环境的相互结合,通常的办法就是将路径以context-param的方式注册并使用ContextLoaderListener进行监听读取。
        ContextLoaderListener的作用就是启动Web容器时,自动装配ApplicationContext的配置信息,因为它实现了ServletContextListener这个接口,在web.xml配置这个监听器,启动容器时,就会默认的执行它的实现方法,使用ServletContextListener接口,开发者能够在客户端请求提供服务之前向ServletContext中添加任意的对象,这个对象就是在ServletContext启动的时候被初始化,然后在ServletContext整个运行期间都是可见的。
        每一个Web应用都有一个ServletContext与之关联,ServletContext对象在应用启动时被创建,在应用关闭时候被销毁,ServletContext在全局范围内是有效的,类似于应用的一个全局变量。
        在ServletContextListener中的核心逻辑便是初始化WebApplicationContext实例并存在至ServletContext中。

ServletContextListener的使用

        正式分析代码之前我们同样还是首先了解ServletContextListener的使用。

1.创建自定义的ServletContextListener

        首先我们创建ServletContextListener,目标是在系统启动时添加自定义的属性,以便于在全局范围内可以随时调用,系统启动的时候会调用ServletContextListener实现类的contextInitialized方法,所以需要这个方法中实现我们的初始化逻辑。

public class MyDataContextListener implements ServletContextListener {


    private ServletContext context;
    
    // 该方法在 ServletContext 启动后被调用,并准备好处理客户端请求
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        this.context = sce.getServletContext();
        //通过这里可以实现自己的逻辑并将结果记录在属性中
        context.setAttribute("myData","this is my Data");
    }

    //这个方法在ServletContext将要关闭的时候调用
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        this.context = null;
    }


}

2.注册监听器
        在web.xml文件中需要注册自定义的监听器。

<listener>
    <listener-class>com.spring_1_100.test_71_80.test75_springmvc.MyDataContextListener</listener-class>
</listener>
3.测试

        一旦Web应用启动的时候,我们就能在任意的Servlet或者JSP中通过现在的代码获取我们的初始化参数。如下:
        String myData = (String)getServletContext().getAttribute(“myData”);

在这里插入图片描述

Spring中的ContextLoaderListener

        分析了ServletContextListener的使用方式后再来分析Spring中的ContextLoaderListener的实现就容易理解得多,虽然ContextLoaderListener实现的逻辑要复杂得多,但是大致的套路还是万变不离其宗。
        ServletContext启动后会调用ServletContextListener的contextInitialized方法,那么我们就从这个函数开始进行分析。

ContextLoaderListener.java
public void contextInitialized(ServletContextEvent event) {
	//初始化WebApplicationContext
    initWebApplicationContext(event.getServletContext());
}

        这里涉及了一个常用的类WebApplicationContext:在Web应用中,我们会用于WebApplicationContext,WebApplicationContext继承自ApplicationContext,在ApplicationContext的基础上又追加了一些特定的Web操作及属性,非常类似于我们通过编程的方式使用Spring时使用的ClassPathXmlApplicationContext类提供的功能。继续跟踪代码。

ContextLoader.java
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    if (servletContext.getAttribute(WebApplicationContext.class.getName() + ".ROOT") != null) {
    	//web.xml中存在多次ContextLoader的定义,抛出异常
        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 {
        if (this.context == null) {
        	//如果WebApplicationContext为空,创建容器,从后面的代码分析中,我们得知,创建的是XmlWebApplicationContext对象
            this.context = createWebApplicationContext(servletContext);
        }
        //从 图1 得知,XmlWebApplicationContext继承了AbstractRefreshableWebApplicationContext,
        //而 AbstractRefreshableWebApplicationContext实现了ConfigurableWebApplicationContext
        if (this.context instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
            if (!cwac.isActive()) {
            	//上下文尚未刷新->提供服务,例如 设置父上下文,设置应用程序上下文ID等
                if (cwac.getParent() == null) {
                	//如果容器并没有父容器,尝试从servlet容器注入其父容器
                    ApplicationContext parent = loadParentContext(servletContext);
                    //为容器设置父容器
                    cwac.setParent(parent);
                }
                //配置并刷新容器
                configureAndRefreshWebApplicationContext(cwac, servletContext);
            }
        }
        //在servlet容器中注入Spring容器
        servletContext.setAttribute(WebApplicationContext.class.getName() + ".ROOT", 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;
            //打印ContextLoaderListener的初始化时间
            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是XmlWebApplicationContext对象,下面我们看看XmlWebApplicationContext的对象关系图
图1
在这里插入图片描述
        initWebApplicationContext函数中主要的体现了创建WebApplicationContext实例的一个功能构架,从函数中我们看到了初始化的大致步骤。

  1. WebApplicationContext存在性验证
            在配置中只允许声明一次ServletContextListener,多次声明会扰乱Spring的执行逻辑,所以在这里首先做的就是对此验证,在Spring中如果创建了WebApplicationContext实例会记录在ServletContext中以方便全局调用,而使用的key就是WebApplicationContext.class.getName() + “.ROOT”,所以验证的方式就是查看ServletContext实例中是否有对应的key的属性。
    2.创建WebApplication实例
            如果通过验证,则Spring将创建WebApplicationContext实例的工作委托给了createWebApplicationContext函数。
ContextLoader.java
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() + "]");
    }
    //实例化 XmlWebApplicationContext类,并返回
    return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
protected Class<?> determineContextClass(ServletContext servletContext) {
	//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 {
    	//从defaultStrategies中获取WebApplicationContext的名称
        contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
        try {
        	//反射获取XmlWebApplicationContext的Class
            return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
        }
        catch (ClassNotFoundException ex) {
            throw new ApplicationContextException(
                    "Failed to load default context class [" + contextClassName + "]", ex);
        }
    }
}

        那么defaultStrategies是从哪里来的呢?其中,在ContextLoader类中有这样的静态代码块:

ContextLoader.java
private static final Properties defaultStrategies;

static {
    try {
    	//DEFAULT_STRATEGIES_PATH = "ContextLoader.properties"
        ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
        defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
    }
    catch (IOException ex) {
        throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
    }
}

        从上面得知,contextClassName来处于当前环境变量的ContextLoader.properties文件, 我们找到这个文件。
在这里插入图片描述
        从这个文件中,我们看到了org.springframework.web.context.WebApplicationContext对应的是org.springframework.web.context.support.XmlWebApplicationContext类名。因此上述代码中contextClassName对应的是XmlWebApplicationContext类。
        综合以上的代码分析,在初始化的过程中,程序首先会读取ContextLoader类的同目录下的属性文件ContextLoader.properties,并根据其中的配置提取将要实现的WebApplicationContext接口的实现类,并根据这个实现类通过反射的方式进行实例的创建。
3.将实例记录在servletContext中。
4.映射当前的类加载器与创建实例到全局变量currentContextPerThread中。

ContextLoader.java
protected ApplicationContext loadParentContext(ServletContext servletContext) {
    ApplicationContext parentContext = null;
    //如果servlet容器中有locatorFactorySelector或parentContextKey配置,则从容器中获取,否则返回空
    String locatorFactorySelector = servletContext.getInitParameter("locatorFactorySelector");
    String parentContextKey = servletContext.getInitParameter("parentContextKey");

    if (parentContextKey != null) {
        // locatorFactorySelector may be null, indicating the default "classpath*:beanRefContext.xml"
        BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);
        Log logger = LogFactory.getLog(ContextLoader.class);
        if (logger.isDebugEnabled()) {
            logger.debug("Getting parent context definition: using parent context key of '" +
                    parentContextKey + "' with BeanFactoryLocator");
        }
        this.parentContextRef = locator.useBeanFactory(parentContextKey);
        parentContext = (ApplicationContext) this.parentContextRef.getFactory();
    }

    return parentContext;
}
ContextLoader.java
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
        String idParam = sc.getInitParameter("contextId");
        if (idParam != null) {
            wac.setId(idParam);
        }
        else {
            wac.setId(WebApplicationContext.class.getName() + ":" +
                    ObjectUtils.getDisplayString(sc.getContextPath()));
        }
    }
	//Spring容器设置servlet容器
    wac.setServletContext(sc);
    //从servlet容器中获取contextConfigLocation参数,也就是
    //<context-param>
    //	<param-name>contextConfigLocation</param-name>
    //	<param-value>classpath:spring_1_100/config_71_80/spring75_mvc/spring75.xml</param-value>
	//</context-param>
	//也就是param-value标签的值
    String configLocationParam = sc.getInitParameter("contextConfigLocation");
    if (configLocationParam != null) {
        wac.setConfigLocation(configLocationParam);
    }

    //从setConfigLocation方法得知,获取到的环境变量是StandardServletEnvironment对象
    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
    }
	//调用所有实现了ApplicationContextInitializer的initialize方法
    customizeContext(sc, wac);
    //主要为IoC容器Bean的生命周期管理提供条件
    wac.refresh();
}
AbstractRefreshableConfigApplicationContext.java
public void setConfigLocation(String location) {
	//将location以 ,; \t\n 分割
    setConfigLocations(StringUtils.tokenizeToStringArray(location, 
   ",; \t\n"));
}
public void setConfigLocations(String... locations) {
    if (locations != null) {
        Assert.noNullElements(locations, "Config locations must not be null");
        this.configLocations = new String[locations.length];
        for (int i = 0; i < locations.length; i++) {
            this.configLocations[i] = resolvePath(locations[i]).trim();
        }
    }
    else {
        this.configLocations = null;
    }
}

protected String resolvePath(String path) {
    return getEnvironment().resolveRequiredPlaceholders(path);
}
public ConfigurableEnvironment getEnvironment() {
    if (this.environment == null) {
        this.environment = createEnvironment();
    }
    return this.environment;
}
protected ConfigurableEnvironment createEnvironment() {
    return new StandardServletEnvironment();
}

        因为XmlWebApplicationContext继承AbstractRefreshableWebApplicationContext类,因此最终创建环境变量是调用AbstractRefreshableWebApplicationContext的createEnvironment方法,最终创建了StandardServletEnvironment环境变量。
        对于StandardServletEnvironment的创建也没有那么简单,在继续分析之前我们来看看StandardServletEnvironment对象关系。
在这里插入图片描述
        在代码中寻寻觅觅。发现AbstractEnvironment竟然重写的默认的构造方法。

AbstractEnvironment.java
public AbstractEnvironment() {
	//StandardServletEnvironment创建过程中默认调用了customizePropertySources方法
    customizePropertySources(this.propertySources);
    if (this.logger.isDebugEnabled()) {
        this.logger.debug(format(
                "Initialized %s with PropertySources %s", getClass().getSimpleName(), this.propertySources));
    }
}
StandardServletEnvironment.java
protected void customizePropertySources(MutablePropertySources propertySources) {
    propertySources.addLast(new StubPropertySource("servletConfigInitParams"));
    propertySources.addLast(new StubPropertySource("servletContextInitParams"));
    if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
        propertySources.addLast(new JndiPropertySource("jndiProperties"));
    }
    super.customizePropertySources(propertySources);
}
StandardEnvironment.java
protected void customizePropertySources(MutablePropertySources propertySources) {
    propertySources.addLast(new MapPropertySource("systemProperties", getSystemProperties()));
    propertySources.addLast(new SystemEnvironmentPropertySource("systemProperties", getSystemEnvironment()));
}
StandardServletEnvironment.java
public void initPropertySources(ServletContext servletContext, ServletConfig servletConfig) {
    WebApplicationContextUtils.initServletPropertySources(getPropertySources(), servletContext, servletConfig);
}

        经过上面StandardServletEnvironment创建过程分析,我们己经知道了getPropertySources方法获取的到的propertySources实际上是来自于StandardServletEnvironment实体化过程中的customizePropertySources方法调用。

public MutablePropertySources getPropertySources() {
    return this.propertySources;
}
WebApplicationContextUtils.java
public static void initServletPropertySources(
        MutablePropertySources propertySources, ServletContext servletContext, ServletConfig servletConfig) {

    Assert.notNull(propertySources, "propertySources must not be null");
    //如果servletContext不为空,则用ServletContextPropertySource替换掉
    //StandardServletEnvironment类中的customizePropertySources创建的StubPropertySource
    if (servletContext != null && propertySources.contains("servletContextInitParams") &&
            propertySources.get("servletContextInitParams") instanceof StubPropertySource) {
        propertySources.replace("servletContextInitParams",
                new ServletContextPropertySource("servletContextInitParams", servletContext));
    }
    //如果servletConfig不为空,则用
    //ServletConfigPropertySource替换掉StandardServletEnvironment类中的
    //customizePropertySources方法创建的StubPropertySource
    if (servletConfig != null && propertySources.contains("servletConfigInitParams") &&
            propertySources.get("servletConfigInitParams") instanceof StubPropertySource) {
        propertySources.replace("servletConfigInitParams",
                new ServletConfigPropertySource("servletConfigInitParams", servletConfig));
    }
}
ContextLoader.java
protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) {
    List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses =
            determineContextInitializerClasses(sc);

    for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) {
        Class<?> initializerContextClass =
                GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class);
        if (initializerContextClass != null) {
            Assert.isAssignable(initializerContextClass, wac.getClass(), String.format(
                    "Could not add context initializer [%s] since its generic parameter [%s] " +
                    "is not assignable from the type of application context used by this " +
                    "context loader [%s]: ", initializerClass.getName(), initializerContextClass.getName(),
                    wac.getClass().getName()));
        }
        this.contextInitializers.add(BeanUtils.instantiateClass(initializerClass));
    }
	//以ApplicationContextInitializer实例上配置的Order注解作为排序条件排序,值越小,越先被调用
    AnnotationAwareOrderComparator.sort(this.contextInitializers);
    for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
    	//调用所有的ApplicationContextInitializer的initialize方法
        initializer.initialize(wac);
    }
}
ContextLoader.java
protected List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>
        determineContextInitializerClasses(ServletContext servletContext) {

    List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> classes =
            new ArrayList<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>();
	//如果想使用globalInitializerClasses参数,则可以在web.xml下配置
	//<context-param>
    //	<param-name>globalInitializerClasses</param-name>
    //	<param-value>com.spring_1_100.test_71_80.test75_springmvc.MyApplicationContextInitializer</param-value>
	//</context-param>
	//param-value标签内容可以以,; \t\n隔开
    String globalClassNames = servletContext.getInitParameter("globalInitializerClasses");
    if (globalClassNames != null) {
        for (String className : StringUtils.tokenizeToStringArray(globalClassNames, ",; \t\n")) {
            classes.add(loadInitializerClass(className));
        }
    }
	//如果想使用contextInitializerClasses参数,则可以在web.xml下配置
	//<context-param>
    //	<param-name>contextInitializerClasses</param-name>
    //	<param-value>com.spring_1_100.test_71_80.test75_springmvc.MyApplicationContextInitializer</param-value>
	//</context-param>
    String localClassNames = servletContext.getInitParameter("contextInitializerClasses");
    if (localClassNames != null) {
        for (String className : StringUtils.tokenizeToStringArray(localClassNames, ",; \t\n")) {
            classes.add(loadInitializerClass(className));
        }
    }
    return classes;
}
ContextLoader.java
private Class<ApplicationContextInitializer<ConfigurableApplicationContext>> loadInitializerClass(String className) {
    try {
        Class<?> clazz = ClassUtils.forName(className, ClassUtils.getDefaultClassLoader());
        Assert.isAssignable(ApplicationContextInitializer.class, clazz);
        return (Class<ApplicationContextInitializer<ConfigurableApplicationContext>>) clazz;
    }
    catch (ClassNotFoundException ex) {
        throw new ApplicationContextException("Failed to load context initializer class [" + className + "]", ex);
    }
}
AbstractApplicationContext.java
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // 1.调用容器准备刷新的方法,获取容器的当前时间,同时给容器设置同步标识
        prepareRefresh();
        
        // 2.告诉子类启动refreshBeanFactory()方法,Bean定义资源文件的载入从子类 的refreshBeanFactory()方法启动
        // 在refresh()方法中 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory() 启动了Bean的注册
        // Bean定义资源的载入,注册过程,finishBeanFactoryInitialization() 方法是对注册后的Bean定义中的预实例化(lazy-init=false)
        // Spring 默认进行预实例化,即为true的Bean 进行处理的地方
        // 初始化 bean ,并进行xml 文件的读取
        // obtainFreshBeanFactory 方法从字面的理解是获取 BeanFactory ,之前有说过,ApplicationContext 是对 BeanFactory
        // 的功能上基础上添加了大量的扩展应用,那么 obtainFreshBeanFactory 正是实现 BeanFactory 的地方,也就是经过这个函数之后
        // ApplicationContext 就已经拥有 BeanFactory 的全部功能
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        
        // 3.为 BeanFactory 进行各种功能进行填充
        prepareBeanFactory(beanFactory);
        
        try {
            // 4. 子类覆盖方法做额外的处理 
            postProcessBeanFactory(beanFactory);    
            // 5.激活各种 BeanFactory 处理器
            invokeBeanFactoryPostProcessors(beanFactory);

            // 6.为BeanFactory注册Post事件处理器
            // BeanPostProcessor(BeanFactory)
            // 注册拦截 bean 创建 bean 处理器,这里只是注册,真正的调用在 getBean 时候
            registerBeanPostProcessors(beanFactory);
            
            // 7.为上下文初始化 Message源,即不同的语言的消息体,国际化处理
            initMessageSource();
            
            // 8.初始化应用消息广播器,并放入到"applicationEventMulticaster" bean 中
            initApplicationEventMulticaster();
            
            // 9. 留给子类来初始化其他的 Bean
            onRefresh();

            // 10.在所有的注册的 bean 中查找 Listener Bean ,注册到消息广播器中
            registerListeners();
            
            // 11.初始化所有剩余的单例Bean
            finishBeanFactoryInitialization(beanFactory);
            
            // 12.完成刷新过程,通知生命周期处理器 lifecycleProcessor 刷新
            // 过程,同时发出 contextRefreshEvent 通知别人
            finishRefresh();
        } catch (BeansException ex) {
            logger.warn("Exception encountered during context initialization - cancelling refresh attempt", ex);
            // 13.销毁已经创建的bean
            destroyBeans();
            // 14.取消刷新操作,重置容器的同步标识
            cancelRefresh(ex);
            throw ex;
        } finally {
            // 设置公共缓存
            resetCommonCaches();
        }
    }
}

下面概括一下 ClassPathXmlApplicationContext 初始化的步骤,并从中解释一下它为我们提供的功能

  1. 初始化前的准备工作,例如对系统属性或者环境变量进行准备及验证
    在某种情况下,项目的使用需要读取某些系统变量,而这个变量的设置很可能会影响到系统的正确性,那么 ClassPathXmlApplicationContext
    为我们提供的准备函数就显得非常的必要了,它可以在 Spring 启动的时候提前对必需的变量进行存在性验证
  2. 初始化 beanFactory 进行 Xml 文件的读取
    之前有提到的 ClasspathXmlApplicationContext包含着 BeanFactory 所提供的一切特征,在这一步骤中将会复用 BeanFActory 中的配置
    文件读取及解析其他的功能,这一步之后,ClassPathXmlApplicationContext 实际上就已经包含了 BeanFactory 所提供的功能,也就是可以
    进行 Bean 的提取等基础操作了
  3. 对 BeanFactory 进行各种功能的填充
    @Qualifier 与@Autowired 应该是大家非常熟悉的注解了,那么这两个注册正是这一步骤增加的支持
  4. Spring 之所以强大,除了它功能上为大家提供了便例外,还有一方面它的完美架构,开放式的架构让使用它的程序员很容易根据业务需要扩展
    已经存在的功能,这种开放式的设置在 Spring中随处可见,例如在配合中就提供了一个空间函数的实现,postProcessBeanFactory 来方便程序员
    在业务上做进步的扩展
  5. 激活各种 beanFactory 的处理器
  6. 注册拦截 bean 创建的 bean 处理器,这里只是注册,真正的调用是在 getBean时候
  7. 为上下文初始化 Message源,即对不同的语言的消息进行国际化的处理
  8. 初始化应用的消息广播器,并放入到"applicationEventMulticaster" bean 中
  9. 留给子类来初始化其他的 bean
  10. 所有的注册的 bean 中查找 listener bean 注册到的消息广播器中
  11. 初始化剩下的单例(非惰性的)
  12. 完成刷新的过程,通知生命周期处理器 lifecycleProcessor 刷新过程,同时发出 contextRefreshEvent 通知别人

        因为这里涉及到整个Spring容器的初始化流程,所以就不再深入研究了,但是我们要知道的一点的就是contextConfigLocation中配置的 classpath:spring_1_100/config_71_80/spring75_mvc/spring75.xml己经被初始化到容器中。

DispatcherServlet

        在Spring中,ContextLoaderListener只是辅助功能,用于创建WebApplicationContext类型实例,而真正的逻辑实现是在DispatcherServlet中进行的,DispatcherServlet是实现Servlet接口的实现类。
        servlet是一个Java编写和程序,此程序是基于HTTP协义的,在服务器端运行的(如Tomcat)是按照servlet规范写的一个Java类,主要是处理客户端的请求并将其结果发送到客户端,servlet的生命周期是由servlet容器来控制的,它可以分为3个阶段:初始化,运行和销毁。
1.初始化阶段

  • servlet容器加载servlet类,把servlet类的.class文件中的数据读取到内存中。
  • servlet容器创建一个ServletConfig对象,ServletConfig对象包含了servlet的初始化配置信息。
  • servlet容器创建一个servlet对象。
  • servlet容器调用servlet对象的init方法进行初始化。

2.运行阶段
        当servlet容器接收到一个请求时,servlet容器会针对这个请求创建servletRequest和servletResponse对象,然后调用servlet方法,并把这个参数传递给servlet方法,servlet方法通过servletRequest对象获取请求信息,并处理请求,再通过servletResponse对象生成这个请求的响应结果,然后销毁servletRequest和servletResponse对象,我们不管请求的是post提交还是get提交,最终这个请求都会由service方法来处理。
3.销毁阶段
        当Web应用被终止时,servlet容器会先调用servlet对象的destory方法,然后再销毁servlet对象,同时也会销毁与servlet对象相关联的servletConfig对象,我们可以在destory方法的实现中,释放servlet所占用的资源,如关闭数据库连接,关闭文件输入输出流等。
        servlet的框架是由两个Java包组成,javax.servlet和javax.servlet.http,在javax.servlet包中定义了所有的servlet类都必需实现或扩展通用的接口和类,在javax.servlet.http包中定义了采用HTTP通信协义的HttpServlet类。
        servlet被设计成请求驱动 ,servlet的请求可能包含多个数据项,当Web容器接收到某个servlet请求时,servlet把请求封装成一个HttpServletRequest对象,然后把对象传给servlet的对应的服务方法。
        HTTP的请求方式包括delete,get,options,post,put和trace,在HttpServlet类中分别提交了相应的服务方法,它们是doDelete(),doGet(),doOptions(),doPost(),doPut()和doTrace()。

servlet的使用

        我们同样还是以简单的servlet来快速体验其用法。

public class MyServlet extends HttpServlet {

    public void init() {
        System.out.println("this is init method ");
    }


    public void doGet(HttpServletRequest request, HttpServletResponse response) {
        handleLogic(request, response);
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response) {
        handleLogic(request, response);
    }

    private void handleLogic(HttpServletRequest request, HttpServletResponse response) {
        System.out.println("handle myLogic ");
        ServletContext sc = getServletContext();
        RequestDispatcher rd = null;
        rd = sc.getRequestDispatcher("/index.jsp");
        try {
            rd.forward(request, response);
        } catch (ServletException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

        麻雀虽小,五脏俱全,实例中包含了对init方法和get/post方法的处理,init方法保证了servlet加载的时候能做一些逻辑操作,而HttpServlet类则会帮助我们根据方法类型的不同而将逻辑引入不同的函数,在子类中我们只需要重写对应的函数逻辑便可,如以上的代码重写了doGet和doPost方法并将逻辑处理部分引导到handleLogic函数中,最后,又将页面跳转至index.jsp
2.添加配置
        为了使servlet能够正常使用,需要在web.xml文件中添加以下的配置。

<servlet>
	<servlet-name>myservlet</servlet-name>
	<servlet-class>test.servlet.MyServlet</servlet-class>
	<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
	<servlet-name>myservlet</servlet-name>
	<url-pattern>*.htm</url-pattern>
</servlet-mapping>

        配置后便可以根据对应的配置访问相应的路径了

DispatcherServlet的初始化

        通过上面的实例我们了解到,在servlet初始化的阶段会调用其init方法,所以我们首先要查看的是DispatcherServlet中是否重写了init方法,我们在其父类HttpServletBean中找到该方法。

HttpServletBean.java
public final void init() throws ServletException {
	if (logger.isDebugEnabled()) {
		logger.debug("Initializing servlet '" + getServletName() + "'");
	}
	
	try {
		//解析init-parm并封装至pvs中
		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
		//将当前的这个servlet类转化为一个BeanWrapper,从而能够以Spring的方式来对init-param的值进行注入
		BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
		ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
		//注册自定义属性编辑器,一旦遇到Resources类型的属性将会使用ResourceEditor进行解析
		bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
		//空实现,留给子类覆盖
		initBeanWrapper(bw);
		//属性注入
		bw.setPropertyValues(pvs, true);
	}
	catch (BeansException ex) {
		logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
		throw ex;
	}
	//留给子类扩展
	initServletBean();
	if (logger.isDebugEnabled()) {
		logger.debug("Servlet '" + getServletName() + "' configured successfully");
	}
}

        DispatcherServlet的初始化过程主要是通过将当前的servlet类型实例转化为BeanWrapper类型的实例,以便使用Spring中提供的功能进行对应的属性注入,这些属性如contextAttribute,contextClass,nameSpace,contextConfigLocation等,都可以在web.xml文件中以初始化参数的方式配置在servlet声明中,DispacherServlet继承自FrameworkServlet,FrameworkServlet类上包含对应的同名属性,Spring会保证这些参数被注入到对应的值中,属性注入主要包含以下的几个步骤。

  1. 封装及验证初始化参数
ServletConfigPropertyValues.java
public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
	throws ServletException {

	Set<String> missingProps = (requiredProperties != null && !requiredProperties.isEmpty()) ?
			new HashSet<String>(requiredProperties) : null;

	Enumeration<String> en = config.getInitParameterNames();
	while (en.hasMoreElements()) {
		String property = en.nextElement();
		Object value = config.getInitParameter(property);
		addPropertyValue(new PropertyValue(property, value));
		if (missingProps != null) {
			missingProps.remove(property);
		}
	}
	if (missingProps != null && missingProps.size() > 0) {
		throw new ServletException(
			"Initialization from ServletConfig for servlet '" + config.getServletName() +
			"' failed; the following required properties were missing: " +
			StringUtils.collectionToDelimitedString(missingProps, ", "));
	}
}

        从代码中得知,封装属性主要对初始化的参数进行封装,也就是servlet中配置的<init-param>中配置的封装,当然,用户也可能通过对requiredProperties参数的初始化来强制验证某些属性的必要性,这样,在属性封装的过程中,一旦检测到requiredProperties中的属性没有指定的初始化值,就会抛出异常。

  1. 将当前的servlet实例转化成BeanWrapper实例。
    PropertyAccessorFactory.forBeanPropertyAccess是Spring中提供的工具方法,主要是用于将指定实例转化为Spring中可以处理的BeanWrapper类型的实例。
    BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);

  2. 注册相对于Resource的属性编辑器
    属性编辑器,我们在之前的博客中己经介绍了并且分析过其工作原理,这里使用属性编辑器的目的是在对当前实例(DispatcherServlet)属性注入的过程中一旦遇到Resource类型的属性就会使用ResourceEditor去解析。

  3. 属性注入
    BeanWrapper为Spring中的方法,支持Spring的自动注入,其实我们最常用的属性注入无非是contextAttribute,contextClass,nameSpace,contextCofigLocation等。

  4. servletBean的初始化

        在ContextLoaderListener加载的时候己经创建了WebApplicationContext的实例,而在这个函数中最重要的就是对这个实例进一步的补充初始化。
        继续查看initServletBean(),父类FrameworkServlet覆盖了HttpServletBean中的initServletBean函数,如下:

FrameworkServlet.java
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");
	}
}

        上面的函数设置了充电器来统计初始化的执行时间,而且提供了一个扩展方法initFrameworkServlet()用于子类的覆盖操作,而作为关键的初始化逻辑实现委托给我了initWebApplicationContext()。

WebApplicationContext的初始化

        initWebApplicationContext函数的主要工作就是创建或刷新WebApplicationContext实例并对servlet功能所使用的变量进行初始化。

FrameworkServlet.java
protected WebApplicationContext initWebApplicationContext() {
	WebApplicationContext rootContext =
			WebApplicationContextUtils.getWebApplicationContext(getServletContext());
	WebApplicationContext wac = null;

	if (this.webApplicationContext != null) {
		//context实例在构造函数中被注入
		wac = this.webApplicationContext;
		if (wac instanceof ConfigurableWebApplicationContext) {
			ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
			if (!cwac.isActive()) {
				if (cwac.getParent() == null) {
					cwac.setParent(rootContext);
				}
				//刷新上下文环境
				configureAndRefreshWebApplicationContext(cwac);
			}
		}
	}
	if (wac == null) {
		//根据contextAttribute属性加载WebApplicationContext
		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;
	}
}

        对于本函数中的初始化主要包含几个部分。

  1. 寻找或创建对应的WebApplicationContext实例

        WebApplicationContext的寻找及创建包括以下的几个步骤。
        通过构造函数注入进行初始化。
        当进入initWebApplicationContext函数后通过判断this.webApplicationContext !=null后,便可以确定this.webApplicationContext是否通过构造函数来初始化的,可是读者可能会有疑问,在initServletBean函数中明明是把创建好的实例记录了this.webAppliationContext中:
        this.webApplicationContext = initWebApplicationContext();
        何以判断这个参数是通过构造函数初始化,而不是通过上一次函数返回的初始化值呢?如果存在这个问题,那么就是读者忽略了一个问题,在Web中包含了Spring的核心逻辑是DispatcherServlet只可以被声明一次,在Spring中己经存在验证,所以这就确保了如果this.webApplicationContext !=null ?则可以直接判定this.webApplicationContext己经通过构造函数初始化了。
        通过web.xml文件中配置的servlet参数contextAttribute来查找ServletContext中对应的属性,默认是WebApplicationContext.class.getName()+".ROOT",也就是在ContextLoaderListener加载时会创建WebApplicationContext实例,并将实例以WebApplicationContext.class.getName()+".ROOT"为key放入到ServletContext中,当然读者可以重写初始化逻辑使用自己创建的WebApplicationContext,并在servlet的配置中通过初始化参数contextAttribute指定key。

FrameworkServlet.java
protected WebApplicationContext findWebApplicationContext() {
	String attrName = getContextAttribute();
	if (attrName == null) {
		return null;
	}
	WebApplicationContext wac =
			WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
	if (wac == null) {
		throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
	}
	return wac;
}
  1. 重新创建WebApplicationContext实例。

        如果通过以上的两种情况没有找到任何突破,那就没有办法了,只能这里创建新的实例了。

FrameworkServlet.java
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
	return createWebApplicationContext((ApplicationContext) parent);
}
FrameworkServlet.java
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
	//获取servlet的初始化参数contextClass,如果没有配置默认的,则使用XmlWebApplicationContext.class
	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");
	}
	//通过反射方法实例化contextClass
	ConfigurableWebApplicationContext wac =
			(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

	wac.setEnvironment(getEnvironment());
	//parent为在ContextLoaderListener中创建的实例
	//在ContextLoaderListener加载的时候初始化的WebApplicationContext类型实例
	wac.setParent(parent);
	//获取contextConfigLocation属性,配置的servlet初始化参数中
	wac.setConfigLocation(getContextConfigLocation());
	//初始化Spring环境包括加载配置文件等
	configureAndRefreshWebApplicationContext(wac);

	return wac;
}
  1. configureAndRefreshWebApplicationContext

        无论是通过构造函数还是单独创建,都会调用configureAndRefreshWebApplicationContext方法来对己经创建的WebApplicationContext实例进行配置及刷新,那么这个步骤又做了哪些工作呢?

FrameworkServlet.java
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());
		}
	}
	//设置ServletContext上下文
	wac.setServletContext(getServletContext());
	//设置ServletConfig
	wac.setServletConfig(getServletConfig());
	//设置命名空间,默认是servlet-name+-servlet
	//本次的命名空间是spring_tiny-servelt
	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);
	//加载配置文件及整合parent到wac中
	wac.refresh();
}

在这里插入图片描述
        无论调用方式如何变化,只要是使用了ApplicationContext所提供的功能最后都免不了使用公共父类AbstractApplicationContext提供的refresh()方法配置文件加载。

protected void postProcessWebApplicationContext(ConfigurableWebApplicationContext wac) {
}
SourceFilteringListener.java
public class SourceFilteringListener implements GenericApplicationListener, SmartApplicationListener {

	private final Object source;

	private GenericApplicationListener delegate;

	public SourceFilteringListener(Object source, ApplicationListener<?> delegate) {
		this.source = source;
		//显然ContextRefreshListener并没有实现GenericApplicationListener
		this.delegate = (delegate instanceof GenericApplicationListener ?
				(GenericApplicationListener) delegate : new GenericApplicationListenerAdapter(delegate));
	}
}

        其他文件的解析和初始化都己经完成,但是/WEB-INF/spring_tiny-servlet.xml文件到目前为止都没有被初始化,那这个文件在哪里被加载的呢?
在这里插入图片描述
        发现在loadBeanDefinitions方法中被加载,那么这个方法是在哪里被调用的呢?我们继续向前找。通过下图得知,原来是在调用obtainFreshBeanFactory方法是被初始化。
在这里插入图片描述
        但是还是不明确 ,到底是在Dispatcher的init方法中调用,还是在ContextLoaderListener的contextInitialized方法呢?我们继续向前查找。
在这里插入图片描述
        最终发现在Dispatcher的init方法内初始化。我们回到初始化加载xml这段代码。

XmlWebApplicationContext.java
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
	String[] configLocations = getConfigLocations();
	if (configLocations != null) {
		for (String configLocation : configLocations) {
			//load WEB-INF/spring_tiny-servlet.xml
			reader.loadBeanDefinitions(configLocation);
		}
	}
}
AbstractRefreshableWebApplicationContext.java
public String[] getConfigLocations() {
	return super.getConfigLocations();
}
AbstractRefreshableConfigApplicationContext
protected String[] getConfigLocations() {
	return (this.configLocations != null ? this.configLocations : getDefaultConfigLocations());
}

protected String[] getDefaultConfigLocations() {
	if (getNamespace() != null) {
		return new String[] {"/WEB-INF/" + getNamespace() + ".xml"};
	}
	else {
		return new String[] {"/WEB-INF/applicationContext.xml"};
	}
}

        当进行DispacherServlet的init化的时候,并没有设置configLocations,但是设置了namespace,因此在容器初始化时会加载spring_tiny-servlet.xml文件内的bean。

AbstractApplicationContext.java
protected void finishRefresh() {
	initLifecycleProcessor();

	// Propagate refresh to lifecycle processor first.
	getLifecycleProcessor().onRefresh();
	
	// Publish the final event.
	publishEvent(new ContextRefreshedEvent(this));

	// Participate in LiveBeansView MBean, if active.
	LiveBeansView.registerApplicationContext(this);
}
AbstractApplicationContext.java
@Override
public void publishEvent(ApplicationEvent event) {
	publishEvent(event, null);
}

AbstractApplicationContext.java
protected void publishEvent(Object event, ResolvableType eventType) {
	Assert.notNull(event, "Event must not be null");
	if (logger.isTraceEnabled()) {
		logger.trace("Publishing event in " + getDisplayName() + ": " + event);
	}

	// Decorate event as an ApplicationEvent if necessary
	ApplicationEvent applicationEvent;
	if (event instanceof ApplicationEvent) {
		applicationEvent = (ApplicationEvent) event;
	}
	else {
		applicationEvent = new PayloadApplicationEvent(this, event);
		if (eventType == null) {
			eventType = ResolvableType.forClassWithGenerics(PayloadApplicationEvent.class, event.getClass());
		}
	}

	// Multicast right now if possible - or lazily once the multicaster is initialized
	if (this.earlyApplicationEvents != null) {
		this.earlyApplicationEvents.add(applicationEvent);
	}
	else {
		getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
	}

	// Publish event via parent context as well...
	if (this.parent != null) {
		if (this.parent instanceof AbstractApplicationContext) {
			((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
		}
		else {
			this.parent.publishEvent(event);
		}
	}
}
SimpleApplicationEventMulticaster.java
public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
	ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
	for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
		Executor executor = getTaskExecutor();
		if (executor != null) {
			executor.execute(new Runnable() {
				@Override
				public void run() {
					invokeListener(listener, event);
				}
			});
		}
		else {
			invokeListener(listener, event);
		}
	}
}
SimpleApplicationEventMulticaster.java
protected void invokeListener(ApplicationListener listener, ApplicationEvent event) {
	ErrorHandler errorHandler = getErrorHandler();
	if (errorHandler != null) {
		try {
			listener.onApplicationEvent(event);
		}
		catch (Throwable err) {
			errorHandler.handleError(err);
		}
	}
	else {
		listener.onApplicationEvent(event);
	}
}

SourceFilteringListener.java
public void onApplicationEvent(ApplicationEvent event) {
	if (event.getSource() == this.source) {
		onApplicationEventInternal(event);
	}
}
SourceFilteringListener.java
protected void onApplicationEventInternal(ApplicationEvent event) {
	if (this.delegate == null) {
		throw new IllegalStateException(
				"Must specify a delegate object or override the onApplicationEventInternal method");
	}
	this.delegate.onApplicationEvent(event);
}
ContextRefreshListener.java
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

	@Override
	public void onApplicationEvent(ContextRefreshedEvent event) {
		FrameworkServlet.this.onApplicationEvent(event);
	}
}
FrameworkServlet.java
public void onApplicationEvent(ContextRefreshedEvent event) {
	this.refreshEventReceived = true;
	onRefresh(event.getApplicationContext());
}
  1. 刷新

        onReresh是FrameworkServlet类中提供的模板方法,在其子类DispatcherServlet进行了重写,主要过程用于刷新Spring在Web功能实现中所有必需使用的全局变量,下面我们会介绍它们的初始化过程以及使用场景,而至于具体的使用细节在稍后再来做详细的介绍。

DispatcherServlet.java
protected void onRefresh(ApplicationContext context) {
	initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
	//(1) 初始化MultipartResolver
	initMultipartResolver(context);
	//(2) 初始化LocaleResolver
	initLocaleResolver(context);
	//(3) 初始化ThemeResolver
	initThemeResolver(context);
	//(4) 初始化HandlerMappings
	initHandlerMappings(context);
	//(5) 初始化HandlerAdapters
	initHandlerAdapters(context);
	//(6) 初始化HandlerExceptionResolvers
	initHandlerExceptionResolvers(context);
	//(7) 初始化RequestToViewNameTranslator
	initRequestToViewNameTranslator(context);
	//(8)初始化ViewResolvers
	initViewResolvers(context);
	//(9)初始化FlashMapManager
	initFlashMapManager(context);
}
  1. 初始化MultipartResolver

        在Spring中,MultipartResolver主要用来处理文件上传,默认的情况下,Spring是没有multipart处理,因为一些开发者想要自己处理他们,如果想使用Spring的multipart,则需要在Web应用上下文中添加multipart解析器,这样,每个请求就会被检查是否包含multipart,然而,如果请求中包含了multipart解析器,这样,每个请求就会被检查到是否包含multipart,然而,如果请求中包含multipart,那么上下文中定义的MultipartResolver就会解析它,这样请求中的multipart属性就会像其他的属性一样被处理,常用配置如下:

<bean id="multipartResolver"  class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    //<!--该属性用来配置可上传文件的最大字节数-->
    <property name="maxInMemorySize" >
        <value>10000</value>
    </property>
</bean>

        当然,CommonsMultipartResolver还提供了其他功能用于帮助用户完成上传功能,有兴趣的读者可以看一下。
        那么MultipartResolver就是在initMultipartResolver中被加入到DispatcherServlet中的。

DispatcherServlet.java
private void initMultipartResolver(ApplicationContext context) {
	try {
		this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
		if (logger.isDebugEnabled()) {
			logger.debug("Using MultipartResolver [" + this.multipartResolver + "]");
		}
	}
	catch (NoSuchBeanDefinitionException ex) {
		// Default is no multipart resolver.
		this.multipartResolver = null;
		if (logger.isDebugEnabled()) {
			logger.debug("Unable to locate MultipartResolver with name '" + MULTIPART_RESOLVER_BEAN_NAME +
					"': no multipart request handling provided");
		}
	}
}

        因为之前的步骤己经完成了Spring中配置文件的解析,所以在这里只要在配置文件注册过都可以通过ApplicationContext提供的getBean方法来直接获取对应的bean,进而初始化MultipartResolver中的multipartResolver变量。

  1. 初始化LocaleResolver

        在Spring中国际化配置中一共有3种使用方式

  • 基于Url参数的配置

        通过URL参数来控制国际化,比如你在页面上加一句< href="?locale=zh_CN" > 简体中文</a>来控制项目中使用国际化参数,而提供这个功能就是AcceptHeaderLocaleResolver,默认的参数名为locale,注意大小写,里面放的就是你的提交参数,比如en_US,zh_CN之类,具体的配置如下:
        <bean id=“localeResolver” class=“org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver”></bean>

  • 基于session的配置

        它通过检验的用户会话中的预置属性来解析区域,最常用的是根据用户本次会话过程中的语言设定决定语言各类(例如,用户时选择语言各类,则本次登录周期内统一使用此语言设定),如果该会话属性不存在,它会根据accept-language HTTP 头部确定默认的区域。
        <bean id=“localeResolver” class=“org.springframework.web.servlet.i18n.SessionLocaleResolver”></bean>

  • 基于cookie的国际化配置

        CookieLocaleResolver用于通过浏览器的cookie设置取得Locale对象,这种策略的应用程序不支持会话或者状态必需保存在客户端时有用,配置如下:
        <bean id=“localeResolver” class=“org.springframework.web.servlet.i18n.CookieLocaleResolver”></bean>

        这3种方式都可以解决国际化问题,但是,对于LocalResolver的使用基础是在DispatcherServlet中的初始化。

private void initLocaleResolver(ApplicationContext context) {
	try {
		this.localeResolver = context.getBean("localeResolver", LocaleResolver.class);
		if (logger.isDebugEnabled()) {
			logger.debug("Using LocaleResolver [" + this.localeResolver + "]");
		}
	}
	catch (NoSuchBeanDefinitionException ex) {
		this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
		if (logger.isDebugEnabled()) {
			logger.debug("Unable to locate LocaleResolver with name '" + LOCALE_RESOLVER_BEAN_NAME +
					"': using default [" + this.localeResolver + "]");
		}
	}
}

        提取配置文件中设置LocaleResolver来初始化DispatcherServlet中的localeResolver属性。

  1. 初始化ThemeResolver。

        在Web开发中经常会遇到通过主题Theme来控制网页风格,这将进一步改善用户体验,简单的说,一个主题就是一组静态资源(比如样式表和图片),它们可以影响应用程序的视觉效果,Spring中的主题功能和国际化功能非常相似,Spring主题功能的构成主要包括如下内容。

  • 主题资源

        org.springframework.ui.context.ThemeSource是Spring中主题资源的接口,Spring的主题需要通过ThemeSource接口来实现存放主题信息资源。
        org.springframework.ui.context.ResourceBundleThemeSource是ThemeSource接口默认的实现类(也是通过ResourceBoundle资源的方式定义主题),在Spring中配置如下:

<bean id=“themeSource” class=“org.springframework.ui.context.support.ResourceBundleThemeSource”>
        <property name=“basenamePrefix” value="com.test. "></property>
</bean>
        默认状态下是在类路径根据目录下查找相应的资源文件,也可以通过basenamePrefix来制定,这样,DispatcherServlet就会在com.test包下查找相应的资源。

  • 主题解析器

        ThemeSource定义了一些主题资源,那么不同的用户使用什么主题资源由谁定义呢?org.springframework.web.servlet.ThemeResolver是主题解析器接口,主题解析器的工作便由它的子类来完成。
        对于主题解析器的子类主要由3个比较常用的实现,以主题文件summer.properties为例。

  1. FixedThemeResolver用于选择一个固定的主题。
    <bean id=“themeResolver” class=“org.springframework.web.servlet.theme.FixedThemeResolver”>
            <property name=“defaultThemeName” value=“summer”></property>
    </bean>
    以上的配置的作用是设置主题文件为summper.properties,在整个项目内固定不变。

  2. CookieThemeResolver用于实现用户所选的主题,以cookie的形式存储在客户端机器上,配置如下:
    <bean id=“themeResolver” class=“org.springframework.web.servlet.theme.CookieThemeResolver”>
            <property name=“defaultThemeName” value=“summer”></property>
    </bean>

  3. SessionThemeResolver用于主题保存在用户的HTTP Session中。
    <bean id=“themeResolver” class=“org.springframework.web.servlet.theme.SessionThemeResolver”>
            <property name=“defaultThemeName” value=“summer”></property>
    </bean>
    以上配置用于设置主题名称,并且将该名称保存在用户的HTTPSession中。

  4. AbstractThemeResolver是一个抽象类被SessionThemeResolver和FixedThemeResolver继承,用户也可以继承它来自定义主题解析器。

  • 拦截器

        如果需要根据用户请求来改变主题,那么Spring提供了一个己经实现的拦截器,ThemeChangeInterceptor拦截器了,配置如下:
<bean id=“themeChangeInterceptor” class=“org.springframework.web.servlet.theme.ThemeChangeInterceptor”>
        <property name=“paramName” value=“themeName”/>
</bean>
        其中设置用户的请求参数名为themeName,即URL为?themeName=具体的主题名称,此外还需要在handlerMapping中配置拦截器,当然 需要在HandlerMapping中添加拦截。
<property name=“interceptors”>
        <list>
                <ref local=“themeChangeInterceptor”/>
        </list>
</property>

        了解了主题文件的简单使用方式后,再来查看解析器的初始化工作,与其他变量的初始化工作相同,主题文件解析器的初始化工作并没有任何需要特别说明的地方。

private void initThemeResolver(ApplicationContext context) {
	try {
		this.themeResolver = context.getBean(THEME_RESOLVER_BEAN_NAME, ThemeResolver.class);
		if (logger.isDebugEnabled()) {
			logger.debug("Using ThemeResolver [" + this.themeResolver + "]");
		}
	}
	catch (NoSuchBeanDefinitionException ex) {
		this.themeResolver = getDefaultStrategy(context, ThemeResolver.class);
		if (logger.isDebugEnabled()) {
			logger.debug(
					"Unable to locate ThemeResolver with name '" + THEME_RESOLVER_BEAN_NAME + "': using default [" +
							this.themeResolver + "]");
		}
	}
}
  1. 初始化HandlerMapping。

        当客户端发出Request时DispatcherServlet会将Request提交给HandlerMapping,然后HandlerMapping根据Web Application Context的配置来回传给DispatcherServlet相应的Controller。
        在基于Spring MVC 的web应用程序中,我们可以为DispatcherServlet提供多个HandlerMapping供其使用,DispatcherServlet在先用HandlerMapping的过程中,将根据我们所指定的一系列的HandlerMapping的优先级进行排序,然后优先使用优先级在前的HandlerMapping,如果当前的HandlerMapping能够返回可用的Handler,DispatcherServlet则使用当前返回的Handler进行Web请求的处理,而不再继续询问其他的HandlerMapping,否则,DispatcherServlet将继续按照各个HandlerMapping的优先级进行询问,直到获取一个可用的Handler为止,初始化配置如下:

private void initHandlerMappings(ApplicationContext context) {
	this.handlerMappings = null;

	if (this.detectAllHandlerMappings) {
		// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
		Map<String, HandlerMapping> matchingBeans =
				BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
		if (!matchingBeans.isEmpty()) {
			this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
			// We keep HandlerMappings in sorted order.
			AnnotationAwareOrderComparator.sort(this.handlerMappings);
		}
	}
	else {
		try {
			HandlerMapping hm = context.getBean("handlerMapping", HandlerMapping.class);
			this.handlerMappings = Collections.singletonList(hm);
		}
		catch (NoSuchBeanDefinitionException ex) {
			// Ignore, we'll add a default HandlerMapping later.
		}
	}
	// Ensure we have at least one HandlerMapping, by registering
	// a default HandlerMapping if no other mappings are found.
	if (this.handlerMappings == null) {
		this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
		if (logger.isDebugEnabled()) {
			logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
		}
	}
}

        默认情况下,Spring MVC将加载当前系统所有实现了HandlerMapping的Bean,如果只期望Spring MVC 加载指定的handlermapping时,可以修改web.xml中的DispatcherServlet的初始化参数,将detectAllHandlerMapping的设置为false:

<init-param>
        <param-name>detectAllHandlerMapping</param-name>
        <param-value>false</param-value>
</init-param>
        此时,SpringMVC 将查找名为"handlerMapping"的bean,将作为当前系统中唯一的handlerMapping,如果没有定义handlerMapping的话,则Spring MVC将按照org.springframework.web.servlet.DispatcherServlet所在目录下的DispatcherServlet.properties中所定义的org.springframework.web.servlet.HandlerMapping的内容来加载默认的handlerMapping(用户没有自定义Strategies的情况下)。

  1. 初始化HandlerAdapters

        从名字上也能联想到一个典型的适配器模式的使用,在计算机编程中,适配器模式将一个类的接口适配成用户所期待的,使用适配器,可能使用接口不兼容而无法在一起工作的类协同工作,做法是将类自己的接口包裹在一个己经存在的类中,那么在处理handler时为什么会使用适配器模式呢?回答这个问题我们首先来分析它的初始化逻辑。

private void initHandlerAdapters(ApplicationContext context) {
	this.handlerAdapters = null;

	if (this.detectAllHandlerAdapters) {
		// Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
		Map<String, HandlerAdapter> matchingBeans =
				BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
		if (!matchingBeans.isEmpty()) {
			this.handlerAdapters = new ArrayList<HandlerAdapter>(matchingBeans.values());
			// We keep HandlerAdapters in sorted order.
			AnnotationAwareOrderComparator.sort(this.handlerAdapters);
		}
	}
	else {
		try {
			HandlerAdapter ha = context.getBean("handlerAdapter", HandlerAdapter.class);
			this.handlerAdapters = Collections.singletonList(ha);
		}
		catch (NoSuchBeanDefinitionException ex) {
			// Ignore, we'll add a default HandlerAdapter later.
		}
	}

	// Ensure we have at least some HandlerAdapters, by registering
	// default HandlerAdapters if no other adapters are found.
	if (this.handlerAdapters == null) {
		this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
		if (logger.isDebugEnabled()) {
			logger.debug("No HandlerAdapters found in servlet '" + getServletName() + "': using default");
		}
	}
}

        同样在初始化的过程中涉及了一个变量detectAllHandlerAdapters,detectAllHandlerAdapters的作用和detectAllHandlerMappings类似,只不过作用的对象handlerAdaptor,亦可以通过如下配置来强制系统加载bean name 为"handlerAdapter" handlerAdapter。
<init-param>
        <param-name>detectAllHandlerAdapters</param-name>
        <param-value>false</param-value>
</init-param>

        如果无法找到相应的bean,那么系统会尝试加载默认适配器。

DispatcherServlet.java
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
	String key = strategyInterface.getName();
	String value = defaultStrategies.getProperty(key);
	if (value != null) {
		String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
		List<T> strategies = new ArrayList<T>(classNames.length);
		for (String className : classNames) {
			try {
				Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
				Object strategy = createDefaultStrategy(context, clazz);
				strategies.add((T) strategy);
			}
			catch (ClassNotFoundException ex) {
				throw new BeanInitializationException(
						"Could not find DispatcherServlet's default strategy class [" + className +
								"] for interface [" + key + "]", ex);
			}
			catch (LinkageError err) {
				throw new BeanInitializationException(
						"Error loading DispatcherServlet's default strategy class [" + className +
								"] for interface [" + key + "]: problem with class file or dependent class", err);
			}
		}
		return strategies;
	}
	else {
		return new LinkedList<T>();
	}
}

        在getDefaultStrategies函数中,Spring会尝试从defaultStrategies中加载对应的HandlerAdapter的属性,那么defaultStrategies是如何被初始化的呢?
        当前类DispatcherServlet中存在这样的一段代码块。

static {
	try {
		ClassPathResource resource = new ClassPathResource("DispatcherServlet.properties", DispatcherServlet.class);
		defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
	}
	catch (IOException ex) {
		throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());
	}
}

        在系统加载的时候,defaultStrategies根据当前路径DispatcherServlet.properties来初始化本身,查看DispatcherServlet.propeties中对应的HandlerAdapter的属性。

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
	org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
	org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
	org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
	org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

        由此得知,如果程序员开发人员并没有在配置文件中定义自己的适配器,那么Spring会默认的加载配置文件中的3个适配器。
        作为总控制器的派遣器servlet通过处理器映射得到处理器后,会轮询处理适配器模块,查找能够处理当前HTTP请求的处理适配器的实现,处理器适配器模块根据处理器映射返回的处理器类型,例如简单的控制器类型,注解控制器类型或者远程调用处理器类型,来选择某一个适当的处理器适配器的实现。从而适配当前的HTTP请求。

  • HTTP请求处理器适配器(HttpRequestHandlerAdapter)。

        HTTP请求处理器适配器仅仅支持对HTTP请求处理器的适配,它简单的将HTTP请求对象和响应对象传递给HTTP请求处理器实现,它并不需要返回值,它主要应用在基于HTTP的远程调用的实现上。

  • 简单控制器处理器适配器(SimpleControllerHandlerAdapter)

        这个实现类将HTTP请求适配到一个控制器的实现进行处理,这里的控制器的实现是一个简单的控制器接口的实现,简单控制器处理器适配器被设计成一个框架类的实现,不需要被改写,客户化的业务逻辑通常是在控制器接口的实现类中实现的。

  • 注解方法处理器适配器(AnnotationMethodHandlerAdapter)。

        这个类的实现是基于注解的实现,它需要结合注解的方法映射和注解方法处理协同工作,它通过解析声明在注解控制器的请求映射信息来解析相应的处理器方法来处理当前的 HTTP请求,在处理的过程中,它通过反射来发现探测处理器方法的参数,调用处理器方法,并且映射返回值到模型和控制器对象,最后返回模型和控制器对象给作为主控制器派遣器 Servlet。

        所以我们现在基本上可以回答之前的问题了,Spring 中所使用 Handler 并没有任何特殊的联系,但是为了统一处理,Spring 提供了不同的情况的适配器。

import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class MyExceptionHandler implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        request.setAttribute("exception", ex.toString());
        request.setAttribute("exceptionStack", ex);
        return new ModelAndView("error/exception");
    }
}

        初始化代码如下:

private void initHandlerExceptionResolvers(ApplicationContext context) {
	this.handlerExceptionResolvers = null;

	if (this.detectAllHandlerExceptionResolvers) {
		// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
		Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
				.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
		if (!matchingBeans.isEmpty()) {
			this.handlerExceptionResolvers = new ArrayList<HandlerExceptionResolver>(matchingBeans.values());
			
			// We keep HandlerExceptionResolvers in sorted order.
			AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
		}
	}
	else {
		try {
			HandlerExceptionResolver her =
					context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
			this.handlerExceptionResolvers = Collections.singletonList(her);
		}
		catch (NoSuchBeanDefinitionException ex) {
			// Ignore, no HandlerExceptionResolver is fine too.
		}
	}

	// Ensure we have at least some HandlerExceptionResolvers, by registering
	// default HandlerExceptionResolvers if no other resolvers are found.
	if (this.handlerExceptionResolvers == null) {
		this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
		if (logger.isDebugEnabled()) {
			logger.debug("No HandlerExceptionResolvers found in servlet '" + getServletName() + "': using default");
		}
	}
}
  1. 初始化 RequestToViewNameTranslator。

        当 Controller 处理器方法没有返回一个 View对象或逻辑视图名称时,并且在该方法中没有直接往 response 的输出流里定数据的时候,Spring 就会采用约定好的方式提供一个逻辑视图名称,这个逻辑视图名是对过 Spring 定义的 org.springframework.web.servlet.RequestToViewNameTranslator 接口的 getViewName方法来实现的,我们可以实现自己的 RequestToViewNameTranslator接口来约定好没有返回视图名称的时候如何确定视图名称,Spring己经给我们提供了它自己的一个实现,那就是 org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator。
        在介绍 DefaultRequestToViewNameTranslator 是如何约定视图名称之前,先来看一下它支持用户定义的属性。

  • prefix:前缀,表示约定好白名称需要加上前缀,默认是空串。
  • suffix:后缀,表示约定好的名称需要加上后缀,默认是空串。
  • separator:分隔符,默认是斜杠"/"。
  • stripLeadingSlash:如果首字符是分隔符,是否要去除,默认是 true。
  • stripTrailingSlash:如果最后一个字符是分隔符,是否要去除,默认是 true。
  • tripExcpetion:如果请求的包路径包含扩展名是否要去除,默认为 true。
  • urlDecode:是否需要对 URL解码,默认是 true,它会采用 request 指定的编码或者 ISO-8859–1编码对 URL进行解码。

        当我们没有在 Spring MVC 的配置文件中拖动的定义一个名为 viewNameTranlator的 bean 的时候,Spring 就会为我们提供一个默认的 viewNameTranslator,即 DefaultRequestToViewNameTranslator。
        接下来看一下,当 Controller 处理器方法没有返回逻辑视图名称时,DefaultRquestToViewNameTranslator 是如何约定视图名称的,DefaultRequstToViewNameTranslator 会获取到请求的 URI,然后根据提供的属性做一些改造。把改造后的结果作为视图名称返回,这里以请求路径 http://localhost/app/test/index.html为例,来说明一下 DefaultRequestToViewNameTranslator是如何工作的,该请求路径对应的请求 URI 为/test/index.html,我们来看一下几种情况,它分别是对应的逻辑视图名称是什么?

  • prefix 和 suffix如果都存在,其他的默认值,那么对应返回的逻辑视图名应该是 prefixtest/indexsuffix。
  • stripLeadingSlash 和 stripExtension 都是 false,其他默认,这个时候的逻辑视图名是/test/index.html。
  • 都采用默认的配置时,返回的逻辑视图名称应该是 test/index。

        如果逻辑视图名称跟请求路径相同或者相关关系都是一样的,那么我们就可以采用 Spring 为我们事先约定好的逻辑视图名称返回,这可以大大的简化我们的开发工作,而以上的功能实现关键属性 viewNameTranslator,则是在 initRequestToViewNameTranslator 中完成。

private void initRequestToViewNameTranslator(ApplicationContext context) {
	try {
		this.viewNameTranslator =
				context.getBean("viewNameTranslator", RequestToViewNameTranslator.class);
		if (logger.isDebugEnabled()) {
			logger.debug("Using RequestToViewNameTranslator [" + this.viewNameTranslator + "]");
		}
	}
	catch (NoSuchBeanDefinitionException ex) {
		this.viewNameTranslator = getDefaultStrategy(context, RequestToViewNameTranslator.class);
		if (logger.isDebugEnabled()) {
			logger.debug("Unable to locate RequestToViewNameTranslator with name '" +
					"viewNameTranslator" + "': using default [" + this.viewNameTranslator +
					"]");
		}
	}
}
  1. 初始化 ViewResolvers。

        在 Spring MVC 中,当 Controller 将请求处理结果放到 ModelAndView中以后,DispacherServlet 会根据 ModelAndView 选择合适的视图进行渲染,那么在 SpringMVC中是如何选择合适的 View呢?View 对象是如何创建的呢?答案就是 ViewResolver 中,ViewResolver 接口定义了 resolverViewName 定义,根据 viewName 创建合适的类型的 View 实现。
        那么如何配置 ViewResolver 呢?在 Spring 中,ViewResolver 作为 Spring Bean 存在,可以在 Spring 配置文件中进行配置,例如下面的代码,配置 JSP相关的 viewResolver。
<bean id=“viewResolver” class=“org.springframework.web.servlet.view.InternalResourceViewResolver”>
        <property name=“prefix” value="/WEB-INF/jsp/"></property>
        <property name=“suffix” value=".jsp"></property>
</bean>

        ViewResolvers 属性的初始化工作在 initViewResolvers 中完成。

private void initViewResolvers(ApplicationContext context) {
	this.viewResolvers = null;
	if (this.detectAllViewResolvers) {
		// Find all ViewResolvers in the ApplicationContext, including ancestor contexts.
		Map<String, ViewResolver> matchingBeans =
				BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
		if (!matchingBeans.isEmpty()) {
			this.viewResolvers = new ArrayList<ViewResolver>(matchingBeans.values());
			// We keep ViewResolvers in sorted order.
			AnnotationAwareOrderComparator.sort(this.viewResolvers);
		}
	}
	else {
		try {
			ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);
			this.viewResolvers = Collections.singletonList(vr);
		}
		catch (NoSuchBeanDefinitionException ex) {
			// Ignore, we'll add a default ViewResolver later.
		}
	}

	// Ensure we have at least one ViewResolver, by registering
	// a default ViewResolver if no other resolvers are found.
	if (this.viewResolvers == null) {
		this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
		if (logger.isDebugEnabled()) {
			logger.debug("No ViewResolvers found in servlet '" + getServletName() + "': using default");
		}
	}
}
  1. 初始化 FlashMapManager 。
            Spring MVC Flash Attrributes 提供了一个请求存储属性,可供其他的请求使用,在使用重定向时候非常必要,例如 Post/Redirect/Get模式,Flash attributes 在重定向之前暂存,(就像存在 session 中)以便重定向之后还能使用,并立即删除。
            Spring MVC 有两个主要的抽象来支持 flash attribute ,Flashmap
private void initFlashMapManager(ApplicationContext context) {
	try {
		this.flashMapManager =
				context.getBean("flashMapManager", FlashMapManager.class);
		if (logger.isDebugEnabled()) {
			logger.debug("Using FlashMapManager [" + this.flashMapManager + "]");
		}
	}
	catch (NoSuchBeanDefinitionException ex) {
		this.flashMapManager = getDefaultStrategy(context, FlashMapManager.class);
		if (logger.isDebugEnabled()) {
			logger.debug("Unable to locate FlashMapManager with name '" +
					"flashMapManager" + "': using default [" + this.flashMapManager + "]");
		}
	}
}
DispatcherServlet 的逻辑处理。

        根据之前的示例,我们知道在 HttpServlet 类中分别提供了相应的服务方法,它们是 doDelete(),doGet(),doOptions(),doPost(),doPut()和 doTrace(),它会根据请求的不同形式将程序引导至对应的函数进行处理,这几个函数中最常用的函数无非是 doGet()和 doPost(),那么我们就直接查看 DispatcherServlet 中对于这两个函数的逻辑实现。

@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {

	processRequest(request, response);
}


@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {

	processRequest(request, response);
}

        对于不同的方法,Spring 并没有做特殊的处理,而是统一将程序再一次的引导至processRequest(request,response) 中。

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {
	//记录当前时间,用于计算 web请求的处理时间
	long startTime = System.currentTimeMillis();
	Throwable failureCause = null;

	LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
	LocaleContext localeContext = buildLocaleContext(request);

	RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
	ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

	WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
	asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

	initContextHolders(request, localeContext, requestAttributes);

	try {
		doService(request, response);
	}
	catch (ServletException ex) {
		failureCause = ex;
		throw ex;
	}
	catch (IOException ex) {
		failureCause = ex;
		throw ex;
	}
	catch (Throwable ex) {
		failureCause = ex;
		throw new NestedServletException("Request processing failed", ex);
	}

	finally {
		resetContextHolders(request, previousLocaleContext, previousAttributes);
		if (requestAttributes != null) {
			requestAttributes.requestCompleted();
		}

		if (logger.isDebugEnabled()) {
			if (failureCause != null) {
				this.logger.debug("Could not complete request", failureCause);
			}
			else {
				if (asyncManager.isConcurrentHandlingStarted()) {
					logger.debug("Leaving response open for concurrent processing");
				}
				else {
					this.logger.debug("Successfully completed request");
				}
			}
		}

		publishRequestHandledEvent(request, response, startTime, failureCause);
	}
}

        函数中己经开始对请求的处理,虽然把细节转移到了 doService 函数中实现,但是我们还是不难看出处理请求后所做的准备与处理工作。

  1. 为了保证当前线程的 LocaleContext 以及 RequestAttributes 可以在当前请求后还能恢复,提取当前线程的两个属性。
  2. 根据当前 request 创建对应的 LocaleContext 和 RequestAttributes,并绑定到当前线程中。
  3. 委托给doService 方法进一步处理。
  4. 请求处理结束后恢复线程原始状态。
  5. 请求处理结束后无论成功与否发布事件通知。

        继续查看 doService 方法。

DispatcherServlet.java
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
		if (logger.isDebugEnabled()) {
			String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
			logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
					" processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
		}

		//如果包含request,请保留请求属性的快照,以便能够在包含之后恢复原始属性。
		Map<String, Object> attributesSnapshot = null;
		//如果 request 属性中包含javax.servlet.include.request_uri
		if (WebUtils.isIncludeRequest(request)) {
			attributesSnapshot = new HashMap<String, Object>();
			Enumeration<?> attrNames = request.getAttributeNames();
			while (attrNames.hasMoreElements()) {
				String attrName = (String) attrNames.nextElement();
				if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
					attributesSnapshot.put(attrName, request.getAttribute(attrName));
				}
			}
		}

		//request 请求中设置容器
		request.setAttribute(DispatcherServlet.class.getName() + ".CONTEXT", getWebApplicationContext());
		request.setAttribute(DispatcherServlet.class.getName() + ".LOCALE_RESOLVER", this.localeResolver);
		request.setAttribute(DispatcherServlet.class.getName() + ".THEME_RESOLVER", this.themeResolver);
		request.setAttribute(DispatcherServlet.class.getName() + ".THEME_SOURCE", getThemeSource());
		//从当前 session 中获取 FlashMap,并作为 request 属性保存起来
		FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
		if (inputFlashMap != null) {
			request.setAttribute(DispatcherServlet.class.getName() + ".INPUT_FLASH_MAP", Collections.unmodifiableMap(inputFlashMap));
		}
		request.setAttribute(DispatcherServlet.class.getName() + ".OUTPUT_FLASH_MAP", new FlashMap());
		request.setAttribute(DispatcherServlet.class.getName() + ".FLASH_MAP_MANAGER", this.flashMapManager);

		try {
			doDispatch(request, response);
		}
		finally {
			if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
				if (attributesSnapshot != null) {
					restoreAttributesAfterInclude(request, attributesSnapshot);
				}
			}
		}
	}
	return wac;
}

        我们猜想对请求的处理至少应该包括一些诸如寻找 Handler 并页面跳转的逻辑处理,但是,在 doService 中我们并没有看到想看到的逻辑,相反却同样是一些准备工作,但是这些准备工作是必不可少的,Spring 将己经初始化的功能辅助工具变量,比如 localeResolver,themeReesolver 等设置在 request 属性中,而这些属性会在接下来的处理中派上用场。
        经过层层的准备工作,终于在 doDispatch函数中看到了完整的请求处理过程。

DispatcherServlet.java
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	HttpServletRequest processedRequest = request;
	HandlerExecutionChain mappedHandler = null;
	boolean multipartRequestParsed = false;

	WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

	try {
		ModelAndView mv = null;
		Exception dispatchException = null;

		try {
			//检测是不是文件上传,如果是 MultipartContent 类型的 request 
			//则转换 request 为 MultipartHttpServletRequest 类型的 request
			processedRequest = checkMultipart(request);
			multipartRequestParsed = (processedRequest != request);

			//根据 request 信息寻找对应的 Handler
			mappedHandler = getHandler(processedRequest);
			if (mappedHandler == null || mappedHandler.getHandler() == null) {
				//如果没有找到对应的 Handler,则通过 response反馈错误信息
				noHandlerFound(processedRequest, response);
				return;
			}
			//根据当前的 handler 寻找对应的 HandlerAdapter
			HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

			//如果当前的 handler 支持 last-modified 头处理
			String method = request.getMethod();
			boolean isGet = "GET".equals(method);
			if (isGet || "HEAD".equals(method)) {
				long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
				if (logger.isDebugEnabled()) {
					logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
				}
				if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
					return;
				}
			}
			//拦截器的 preHandler 方法调用。
			//如果任意一个拦截器的preHandle方法返回 false,终止本次访问
			if (!mappedHandler.applyPreHandle(processedRequest, response)) {
				return;
			}

			//真正的激活handler 并返回视图
			mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
			//如果是异步请求,也没有没有什么视图层的渲染了
			if (asyncManager.isConcurrentHandlingStarted()) {
				return;
			}
			//视图名称转换应用于需要添加前缀后缀的情况
			applyDefaultViewName(processedRequest, mv);
			//应用所有拦截器的 postHandle 方法
			mappedHandler.applyPostHandle(processedRequest, response, mv);
		}
		catch (Exception ex) {
			dispatchException = ex;
		}
		processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
	}
	catch (Exception ex) {
		//抛出异常,调用拦截器链中的所有拦截器的afterCompletion方法
		triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
	}
	catch (Error err) {
		triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
	}
	finally {
		if (asyncManager.isConcurrentHandlingStarted()) {
			if (mappedHandler != null) {
				//调用所有异步拦截器的afterConcurrentHandlingStarted方法
				mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
			}
		}
		else {
			if (multipartRequestParsed) {
				//如果存在文件上传,清除掉本地缓存文件
				cleanupMultipart(processedRequest);
			}
		}
	}
}
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
	//获取拦截器链afterCompletion方法
	HandlerInterceptor[] interceptors = getInterceptors();
	if (!ObjectUtils.isEmpty(interceptors)) {
		for (int i = 0; i < interceptors.length; i++) {
			HandlerInterceptor interceptor = interceptors[i];
			//如果任意一个拦截器的preHandle方法返回 false
			if (!interceptor.preHandle(request, response, this.handler)) {
				//调用拦截器链中所有拦截器的afterCompletion方法
				triggerAfterCompletion(request, response, null);
				return false;
			}
			this.interceptorIndex = i;
		}
	}
	return true;
}
private void applyDefaultViewName(HttpServletRequest request, ModelAndView mv) throws Exception {
	if (mv != null && !mv.hasView()) {
		mv.setViewName(getDefaultViewName(request));
	}
}
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
	HandlerInterceptor[] interceptors = getInterceptors();
	if (!ObjectUtils.isEmpty(interceptors)) {
		for (int i = interceptors.length - 1; i >= 0; i--) {
			HandlerInterceptor interceptor = interceptors[i];
			interceptor.postHandle(request, response, this.handler, mv);
		}
	}
}


private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
		HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {

	boolean errorView = false;

	if (exception != null) {
		if (exception instanceof ModelAndViewDefiningException) {
			logger.debug("ModelAndViewDefiningException encountered", exception);
			mv = ((ModelAndViewDefiningException) exception).getModelAndView();
		}
		else {
			Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
			mv = processHandlerException(request, response, handler, exception);
			errorView = (mv != null);
		}
	}

	//如果 Handler 实例处理中返回了 view,那么需要做页面处理
	if (mv != null && !mv.wasCleared()) {
		//处理页面跳转
		render(mv, request, response);
		if (errorView) {
			WebUtils.clearErrorRequestAttributes(request);
		}
	}
	else {
		if (logger.isDebugEnabled()) {
			logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
					"': assuming HandlerAdapter completed request handling");
		}
	}

	if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
		// Concurrent handling started during a forward
		return;
	}

	if (mappedHandler != null) {
		//完成处理后激活触发器
		mappedHandler.triggerAfterCompletion(request, response, null);
	}
}

private void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response,
        HandlerExecutionChain mappedHandler, Exception ex) throws Exception {
	//Spring 考虑到还没有获得 Handler 时就抛出异常,所以mappedHandler可能为空的情况
    if (mappedHandler != null) {
        mappedHandler.triggerAfterCompletion(request, response, ex);
    }
    throw ex;
}

private void triggerAfterCompletionWithError(HttpServletRequest request, HttpServletResponse response,
        HandlerExecutionChain mappedHandler, Error error) throws Exception {
	//封装 Error
    ServletException ex = new NestedServletException("Handler processing failed", error);
    //Spring 考虑到还没有获得 Handler 时就抛出Error,所以mappedHandler可能为空的情况
    if (mappedHandler != null) {
    	//调用拦截器链的所有的afterCompletion方法
        mappedHandler.triggerAfterCompletion(request, response, ex);
    }
    throw ex;
}
触发拦截器链的afterCompletion方法
HandlerExecutionChain.java
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
        throws Exception {

    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        for (int i = this.interceptorIndex; i >= 0; i--) {
            HandlerInterceptor interceptor = interceptors[i];
            try {
                interceptor.afterCompletion(request, response, this.handler, ex);
            }
            catch (Throwable ex2) {
                logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
            }
        }
    }
}

        上述代码其实也很简单,只是获取所有的拦截器,调用每个拦截器的afterCompletion方法。

void applyAfterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response) {
    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        for (int i = interceptors.length - 1; i >= 0; i--) {
            if (interceptors[i] instanceof AsyncHandlerInterceptor) {
                try {
                    AsyncHandlerInterceptor asyncInterceptor = (AsyncHandlerInterceptor) interceptors[i];
                    asyncInterceptor.afterConcurrentHandlingStarted(request, response, this.handler);
                }
                catch (Throwable ex) {
                    logger.error("Interceptor [" + interceptors[i] + "] failed in afterConcurrentHandlingStarted", ex);
                }
            }
        }
    }
}

        doDispatch 函数中展示了 Spring请求处理所涉及的主要逻辑,而我们之前设置在 request 中的各种辅助属性都被派上用场,下面回顾一下逻辑处理的全过程。

protected void cleanupMultipart(HttpServletRequest request) {
    MultipartHttpServletRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);
    if (multipartRequest != null) {
        this.multipartResolver.cleanupMultipart(multipartRequest);
    }
}

        因为multipartResolver的实现类有多个,那我们以 org.springframework.web.multipart.commons.CommonsMultipartResolver为例分析cleanupMultipart方法吧。

CommonsMultipartResolver.java
public void cleanupMultipart(MultipartHttpServletRequest request) {
    if (request != null) {
        try {
            cleanupFileItems(request.getMultiFileMap());
        }
        catch (Throwable ex) {
            logger.warn("Failed to perform multipart cleanup for servlet request", ex);
        }
    }
}
protected void cleanupFileItems(MultiValueMap<String, MultipartFile> multipartFiles) {
    for (List<MultipartFile> files : multipartFiles.values()) {
        for (MultipartFile file : files) {
            if (file instanceof CommonsMultipartFile) {
                CommonsMultipartFile cmf = (CommonsMultipartFile) file;
                //删除临时文件
                cmf.getFileItem().delete();
                if (logger.isDebugEnabled()) {
                    logger.debug("Cleaning up multipart file [" + cmf.getName() + "] with original filename [" +
                            cmf.getOriginalFilename() + "], stored " + cmf.getStorageDescription());
                }
            }
        }
    }
}

        上述逻辑也很简单,就是删除文件上传过程中产生的临时文件。

MultipartContent 类型的 request 处理

        对于请求的处理,Spring 首先会考虑的是对于 Multipart 的处理,如果是 MultipartContent 类型的 request,则转换 request 为 MultipartHttpServletRequest 类型的 request。

protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
	//配置了multipartResolver,并且是 POST 请求,并且contextType是以 multipart/ 开头
	if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
		if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
			logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " +
					"this typically results from an additional MultipartFilter in web.xml");
		}
		else if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) instanceof MultipartException) {
			logger.debug("Multipart resolution failed for current request before - " +
					"skipping re-resolution for undisturbed error rendering");
		}
		else {
			return this.multipartResolver.resolveMultipart(request);
		}
	}
	return request;
}
根据 request 信息寻找对应的 Handler

        在Spring 中最简单的映射处理器配置如下:

<bean id="simpleUrlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="mappings">
        <props>
            <prop key="/userlist.htm">userController</prop>
            <prop key="/helloWorldLastModified.htm">helloWorldLastModifiedCacheController</prop>
        </props>
    </property>
</bean>

        先看一下SimpleUrlHandlerMapping的类结构。
在这里插入图片描述
        我们看到SimpleUrlHandlerMapping实现了 ApplicationContextAware接口,因此,在 bean 的初始化时,会调用ApplicationContextAwareProcessor 的postProcessBeforeInitialization方法。在postProcessBeforeInitialization会调用invokeAwareInterfaces方法,接下来,我们继续跟进代码

ApplicationObjectSupport.java
private void invokeAwareInterfaces(Object bean) {
    if (bean instanceof Aware) {
        if (bean instanceof EnvironmentAware) {
            ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
        }
        if (bean instanceof EmbeddedValueResolverAware) {
            ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(
                    new EmbeddedValueResolver(this.applicationContext.getBeanFactory()));
        }
        if (bean instanceof ResourceLoaderAware) {
            ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
        }
        if (bean instanceof ApplicationEventPublisherAware) {
            ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
        }
        if (bean instanceof MessageSourceAware) {
            ((MessageSourceAware) bean).setMessageSource(this.applicationContext);
        }
        if (bean instanceof ApplicationContextAware) {
            ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
        }
    }
}

WebApplicationObjectSupport.java
public final void setApplicationContext(ApplicationContext context) throws BeansException {
    if (context == null && !isContextRequired()) {
        // Reset internal context state.
        this.applicationContext = null;
        this.messageSourceAccessor = null;
    }
    else if (this.applicationContext == null) {
        // Initialize with passed-in context.
        if (!requiredContextClass().isInstance(context)) {
            throw new ApplicationContextException(
                    "Invalid application context: needs to be of type [" + requiredContextClass().getName() + "]");
        }
        this.applicationContext = context;
        this.messageSourceAccessor = new MessageSourceAccessor(context);
        initApplicationContext(context);
    }
    else {
        // Ignore reinitialization if same context passed in.
        if (this.applicationContext != context) {
            throw new ApplicationContextException(
                    "Cannot reinitialize with different application context: current one is [" +
                    this.applicationContext + "], passed-in one is [" + context + "]");
        }
    }
}
注册 Handler
SimpleUrlHandlerMapping.java
public void initApplicationContext() throws BeansException {
    super.initApplicationContext();
    //注册所有的 handler
    registerHandlers(this.urlMap);
}

        有点疑问,在SimpleUrlHandlerMapping我们明显是用 url初始化了mappings属性,但是这里却是用urlMap来注册 Handler,下面来看看两者之间的关系。

SimpleUrlHandlerMapping.java
public void setMappings(Properties mappings) {
	//将所有的mappings属性复制到 urlMap
    CollectionUtils.mergePropertiesIntoMap(mappings, this.urlMap);
}
SimpleUrlHandlerMapping.java
protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
    if (urlMap.isEmpty()) {
        logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
    }
    else {
        for (Map.Entry<String, Object> entry : urlMap.entrySet()) {
            String url = entry.getKey();
            Object handler = entry.getValue();
			//为 url 添加默认前缀/
            if (!url.startsWith("/")) {
                url = "/" + url;
            }
           //去掉url前后空格
            if (handler instanceof String) {
                handler = ((String) handler).trim();
            }
            registerHandler(url, handler);
        }
    }
}

AbstractUrlHandlerMapping.java
protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
    Assert.notNull(urlPath, "URL path must not be null");
    Assert.notNull(handler, "Handler object must not be null");
    Object resolvedHandler = handler;

    //如果 handler 是字符串,从容器中获取 handler,并且handler 非延迟加载
    if (!this.lazyInitHandlers && handler instanceof String) {
        String handlerName = (String) handler;
        if (getApplicationContext().isSingleton(handlerName)) {
            resolvedHandler = getApplicationContext().getBean(handlerName);
        }
    }

	//如果 url 设置多个处理器,抛出异常
    Object mappedHandler = this.handlerMap.get(urlPath);
    if (mappedHandler != null) {
        if (mappedHandler != resolvedHandler) {
            throw new IllegalStateException(
                    "Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
                    "]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
        }
    }
    else {
    	//设置根处理器
        if (urlPath.equals("/")) {
            if (logger.isInfoEnabled()) {
                logger.info("Root mapping to " + getHandlerDescription(handler));
            }
            setRootHandler(resolvedHandler);
        }
        //设置默认处理器
        else if (urlPath.equals("/*")) {
            if (logger.isInfoEnabled()) {
                logger.info("Default mapping to " + getHandlerDescription(handler));
            }
            setDefaultHandler(resolvedHandler);
        }
        else {
        	//设置普通处理器
            this.handlerMap.put(urlPath, resolvedHandler);
            if (logger.isInfoEnabled()) {
                logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
            }
        }
    }
}

        通过上面的分析,我们知道了 Handler 的由来,下面我们继续跟进代码,看super.initApplicationContext();中又做了什么事情呢?

AbstractHandlerMapping.java
protected void initApplicationContext() throws BeansException {
	//预留给子类实现
    extendInterceptors(this.interceptors);
    //从容器中加载所有的拦截器
    detectMappedInterceptors(this.adaptedInterceptors);
    initInterceptors();
}

        通过上述代码,我们知道了initApplicationContext方法主要用途是初始化拦截器相关的逻辑。我们来看看extendInterceptors方法,发现什么也没有做,也就是 Spring 预留给子类去实现的逻辑。

protected void extendInterceptors(List interceptors) {
}

        下面我们来续续跟进detectMappedInterceptors方法,看是如何从容器中获取拦截器的。

注册拦截器
AbstractHandlerMapping.java
protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {
    mappedInterceptors.addAll(
            BeanFactoryUtils.beansOfTypeIncludingAncestors(
                    getApplicationContext(), MappedInterceptor.class, true, false).values());
}

        程序代码执行到这里,
通过BeanFactoryUtils.beansOfTypeIncludingAncestors()工具类方法获取所有MappedInterceptor类型的 Bean。
在这里插入图片描述
在这里插入图片描述

        但是让我感觉奇怪的是MyTestInterceptor并没有和MappedInterceptor扯上关系了啊。没有办法,只能来看MyTestInterceptor的实例化逻辑了。

<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/list"/>
        <bean class="com.spring_1_100.test_71_80.test75_springmvc.MyTestInterceptor"></bean>
    </mvc:interceptor>
</mvc:interceptors>

        首先,我们来看看<mvc:interceptors … />标签的解析。

public class MvcNamespaceHandler extends NamespaceHandlerSupport {

    @Override
    public void init() {
        registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
        registerBeanDefinitionParser("default-servlet-handler", new DefaultServletHandlerBeanDefinitionParser());
        registerBeanDefinitionParser("interceptors", new InterceptorsBeanDefinitionParser());
        registerBeanDefinitionParser("resources", new ResourcesBeanDefinitionParser());
        registerBeanDefinitionParser("view-controller", new ViewControllerBeanDefinitionParser());
        registerBeanDefinitionParser("redirect-view-controller", new ViewControllerBeanDefinitionParser());
        registerBeanDefinitionParser("status-controller", new ViewControllerBeanDefinitionParser());
        registerBeanDefinitionParser("view-resolvers", new ViewResolversBeanDefinitionParser());
        registerBeanDefinitionParser("tiles-configurer", new TilesConfigurerBeanDefinitionParser());
        registerBeanDefinitionParser("freemarker-configurer", new FreeMarkerConfigurerBeanDefinitionParser());
        registerBeanDefinitionParser("velocity-configurer", new VelocityConfigurerBeanDefinitionParser());
        registerBeanDefinitionParser("groovy-configurer", new GroovyMarkupConfigurerBeanDefinitionParser());
        registerBeanDefinitionParser("script-template-configurer", new ScriptTemplateConfigurerBeanDefinitionParser());
        registerBeanDefinitionParser("cors", new CorsBeanDefinitionParser());
    }
}

        从上述代码中,我们看到了<interceptors />标签的解析器为InterceptorsBeanDefinitionParser,接下来进入InterceptorsBeanDefinitionParser类,看看是如何解析的。

InterceptorsBeanDefinitionParser.java
class InterceptorsBeanDefinitionParser implements BeanDefinitionParser {

    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
        parserContext.pushContainingComponent(compDefinition);

        RuntimeBeanReference pathMatcherRef = null;
        if (element.hasAttribute("path-matcher")) {
            pathMatcherRef = new RuntimeBeanReference(element.getAttribute("path-matcher"));
        }

        List<Element> interceptors = DomUtils.getChildElementsByTagName(element, "bean", "ref", "interceptor");
        for (Element interceptor : interceptors) {
            RootBeanDefinition mappedInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
            mappedInterceptorDef.setSource(parserContext.extractSource(interceptor));
            mappedInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);

            ManagedList<String> includePatterns = null;
            ManagedList<String> excludePatterns = null;
            Object interceptorBean;
            if ("interceptor".equals(interceptor.getLocalName())) {
                includePatterns = getIncludePatterns(interceptor, "mapping");
                excludePatterns = getIncludePatterns(interceptor, "exclude-mapping");
                Element beanElem = DomUtils.getChildElementsByTagName(interceptor, "bean", "ref").get(0);
                interceptorBean = parserContext.getDelegate().parsePropertySubElement(beanElem, null);
            }
            else {
                interceptorBean = parserContext.getDelegate().parsePropertySubElement(interceptor, null);
            }
            mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, includePatterns);
            mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, excludePatterns);
            mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(2, interceptorBean);

            if (pathMatcherRef != null) {
                mappedInterceptorDef.getPropertyValues().add("pathMatcher", pathMatcherRef);
            }

            String beanName = parserContext.getReaderContext().registerWithGeneratedName(mappedInterceptorDef);
            parserContext.registerComponent(new BeanComponentDefinition(mappedInterceptorDef, beanName));
        }

        parserContext.popAndRegisterContainingComponent();
        return null;
    }

    private ManagedList<String> getIncludePatterns(Element interceptor, String elementName) {
        List<Element> paths = DomUtils.getChildElementsByTagName(interceptor, elementName);
        ManagedList<String> patterns = new ManagedList<String>(paths.size());
        for (Element path : paths) {
            patterns.add(path.getAttribute("path"));
        }
        return patterns;
    }

}

        上述代码看上去一大堆,其实原理很简单,就是遍历interceptors下的所有interceptor标签,将所有的mapping中的 path解析成ManagedList<String> includePatterns,exclude-mapping中的 path解析成ManagedList excludePatterns,bean解析成interceptorBean作为MappedInterceptor构造函数的三个参数注入。下面我们来看看MappedInterceptor的构造函数 。

MappedInterceptor.java
public MappedInterceptor(String[] includePatterns, String[] excludePatterns, HandlerInterceptor interceptor) {
    this.includePatterns = includePatterns;
    this.excludePatterns = excludePatterns;
    this.interceptor = interceptor;
}

        因此在detectMappedInterceptors方法中获取到所有的MappedInterceptor,每个MappedInterceptor的interceptor属性就是我们自定义的拦截器com.spring_1_100.test_71_80.test75_springmvc.MyTestInterceptor,下面继续接着看看initInterceptors的内部实现。

AbstractHandlerMapping.java
protected void initInterceptors() {
    if (!this.interceptors.isEmpty()) {
        for (int i = 0; i < this.interceptors.size(); i++) {
            Object interceptor = this.interceptors.get(i);
            if (interceptor == null) {
                throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
            }
            this.adaptedInterceptors.add(adaptInterceptor(interceptor));
        }
    }
}

protected HandlerInterceptor adaptInterceptor(Object interceptor) {
    if (interceptor instanceof HandlerInterceptor) {
        return (HandlerInterceptor) interceptor;
    }
    else if (interceptor instanceof WebRequestInterceptor) {
        return new WebRequestHandlerInterceptorAdapter((WebRequestInterceptor) interceptor);
    }
    else {
        throw new IllegalArgumentException("Interceptor type not supported: " + interceptor.getClass().getName());
    }
}

        从上述代码来看,在initInterceptors方法中,也没有做过多的事情,只是将所有的interceptor加入到了adaptedInterceptors中,只是对于特殊实现了WebRequestInterceptor的拦截器做了特殊的适配转换。接下来,我们来看 Handler 的获取。
        在 Spring 加载的过程中,Spring 会将类型为SimpleUrlHandlerMapping 的实例加载到 this.handlerMappings 中,按照常理推断,根据 request 提取对应的 Handler,无非就是提取当前实例中的 userController,但是 userController 为继承自 AbstractController 类型的实例,与 HandlerExceptionChain 并无任何关联,那么这一步如何封装的呢?

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	//遍历所有的 HandlerMappings,本次测试,只有SimpleUrlHandlerMapping 
	for (HandlerMapping hm : this.handlerMappings) {
		if (logger.isTraceEnabled()) {
			logger.trace(
					"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
		}
		HandlerExecutionChain handler = hm.getHandler(request);
		if (handler != null) {
			return handler;
		}
	}
	return null;
}

        在之前的内容我们提到过,在系统启动时 Spring 会将所有的映射类型的 bean 注册到 this.handlerMappings 变量中,所以此函数的目的就是遍历所有的 HandlerMapping,并调用其 getHandler 方法进行封装处理,所以 SimpleUrlHandlerMapping 为例查看其 getHandler 方法如下:

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	//根据 request 获取对应的 handler
	Object handler = getHandlerInternal(request);
	if (handler == null) {
		//获取默认的处理器,如配置了 / 或 /* ,对应的处理器
		handler = getDefaultHandler();
	}
	//如果也没有提供默认的 handler,则无法继承处理返回 null
	if (handler == null) {
		return null;
	}
	//如果 handler是字符串,则从容器中获取 bean
	if (handler instanceof String) {
		String handlerName = (String) handler;
		handler = getApplicationContext().getBean(handlerName);
	}

	HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
	if (CorsUtils.isCorsRequest(request)) {
		CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(request);
		CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
		CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
		executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
	}
	return executionChain;
}

        函数中首先会使用 getHandlerInternal 方法根据 request 信息获取对应的 Handler,如果以 SimpleUrlHandlerMapping 为例分析,那么我们推断此步骤提供的功能很可能是根据 URL 匹配到 Controller 并返回,当然,如果没有找到对应的 Controller 处理器那么程序会尝试去查找配置中默认的处理器,当然,当查找的 controller 为 String类型时,那就意味着返回的配置的 bean 名称,需要根据 bean 名称查找对应的 bean,最后,还通过 getHandlerExecutionChain 方法返回对应的 Handler 进行封装,以保证满足返回类型的匹配,下面详细分析这个过程。

protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
	//截取用于匹配的 url 有效路径
	String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
	//根据路径寻找 Handler
	Object handler = lookupHandler(lookupPath, request);
	if (handler == null) {
		Object rawHandler = null;
		if ("/".equals(lookupPath)) {
			//如果请求路径是/,那么使用 RootHandler 进行处理
			rawHandler = getRootHandler();
		}
		if (rawHandler == null) {
			//无法找到 handler,则使用默认的 handler
			rawHandler = getDefaultHandler();
		}
		if (rawHandler != null) {
			//根据 bean 名称获取对应的 bean
			if (rawHandler instanceof String) {
				String handlerName = (String) rawHandler;
				rawHandler = getApplicationContext().getBean(handlerName);
			}
			//模版方法
			validateHandler(rawHandler, request);
			handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
		}
	}
	if (handler != null && logger.isDebugEnabled()) {
		logger.debug("Mapping [" + lookupPath + "] to " + handler);
	}
	else if (handler == null && logger.isTraceEnabled()) {
		logger.trace("No handler mapping found for [" + lookupPath + "]");
	}
	return handler;
}

protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
	//直接匹配的情况
	Object handler = this.handlerMap.get(urlPath);
	if (handler != null) {
		// Bean name or resolved handler?
		if (handler instanceof String) {
			String handlerName = (String) handler;
			handler = getApplicationContext().getBean(handlerName);
		}
		validateHandler(handler, request);
		return buildPathExposingHandler(handler, urlPath, urlPath, null);
	}
	//通配符匹配的处理
	List<String> matchingPatterns = new ArrayList<String>();
	for (String registeredPattern : this.handlerMap.keySet()) {
		if (getPathMatcher().match(registeredPattern, urlPath)) {
			matchingPatterns.add(registeredPattern);
		}
		else if (useTrailingSlashMatch()) {
			if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) {
				matchingPatterns.add(registeredPattern +"/");
			}
		}
	}
	String bestPatternMatch = null;
	Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
	if (!matchingPatterns.isEmpty()) {
		Collections.sort(matchingPatterns, patternComparator);
		if (logger.isDebugEnabled()) {
			logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns);
		}
		bestPatternMatch = matchingPatterns.get(0);
	}
	if (bestPatternMatch != null) {
		handler = this.handlerMap.get(bestPatternMatch);
		if (handler == null) {
			Assert.isTrue(bestPatternMatch.endsWith("/"));
			handler = this.handlerMap.get(bestPatternMatch.substring(0, bestPatternMatch.length() - 1));
		}
		
		// Bean name or resolved handler?
		if (handler instanceof String) {
			String handlerName = (String) handler;
			handler = getApplicationContext().getBean(handlerName);
		}
		validateHandler(handler, request);
		String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestPatternMatch, urlPath);

		// There might be multiple 'best patterns', let's make sure we have the correct URI template variables
		// for all of them
		Map<String, String> uriTemplateVariables = new LinkedHashMap<String, String>();
		for (String matchingPattern : matchingPatterns) {
			if (patternComparator.compare(bestPatternMatch, matchingPattern) == 0) {
				Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
				Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
				uriTemplateVariables.putAll(decodedVars);
			}
		}
		if (logger.isDebugEnabled()) {
			logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables);
		}
		return buildPathExposingHandler(handler, bestPatternMatch, pathWithinMapping, uriTemplateVariables);
	}
	// No handler found...
	return null;
}

        根据 URL 获取对应的Handler 的匹配规则代码实现起来虽然很长,但是并不难还是外面,考虑了直接匹配与通配符两种情况,其中要提及的是 buildPathExposingHandler函数,它将 Handler 封装成HandlerExecutionChain类型。

protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern,
		String pathWithinMapping, Map<String, String> uriTemplateVariables) {

	HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler);
	chain.addInterceptor(new PathExposingHandlerInterceptor(bestMatchingPattern, pathWithinMapping));
	if (!CollectionUtils.isEmpty(uriTemplateVariables)) {
		chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables));
	}
	return chain;
}

        在函数中我们看到了通过将 Handler 以参数的形式传入,并构建 HandlerExcutionChain 类型实例,加入了两个拦截器,此时我们似乎己经了解了 Spring 这样在番周折的目的,链处理机制,是 Sring 非常常用的处理方式,是 AOP中重要的组成部分,可以方便的对目标对象进行扩展及拦截,这是非常优秀的设计。

加入拦截器到执行链

        getHandlerExecutionChain函数最主要的上的就是将配置文件中的对应的拦截器加入到执行链中,以保证这些拦截器可以有效的作用于目标对象。

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
	HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
			(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

	String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
	for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
		
		if (interceptor instanceof MappedInterceptor) {
			MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
			//匹配成功,将拦截器加入到拦截器链中
			if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
				chain.addInterceptor(mappedInterceptor.getInterceptor());
			}
		}
		else {
			chain.addInterceptor(interceptor);
		}
	}
	return chain;
}

        经过之前注册拦截器分析得知自定义拦截器都被封装到MappedInterceptor的interceptor的属性中,而mapping中的path属性和exclude-mapping中的path属性分别被封装到MappedInterceptor的includePatterns和excludePatterns属性中,而includePatterns和excludePatterns属性的作用,猜猜都知道是用于匹配。下面我们来看看matches方法。

MappedInterceptor.java
public boolean matches(String lookupPath, PathMatcher pathMatcher) {
    PathMatcher pathMatcherToUse = (this.pathMatcher != null) ? this.pathMatcher : pathMatcher;
    if (this.excludePatterns != null) {
    	//如果uri被excludePatterns匹配到的不符合拦截器条件
        for (String pattern : this.excludePatterns) {
            if (pathMatcherToUse.match(pattern, lookupPath)) {
                return false;
            }
        }
    }
    //如果没有被excludePatterns匹配到,同时也没有配置mapping,则符合拦截器条件
    if (this.includePatterns == null) {
        return true;
    }
    else {
    	//被includePatterns匹配到的,符合拦截器条件
        for (String pattern : this.includePatterns) {
            if (pathMatcherToUse.match(pattern, lookupPath)) {
                return true;
            }
        }
        return false;
    }
}

        上述查找符合条件的拦截器分为四种情况。

  • uri被excludePatterns匹配上,不被拦截器拦截。
  • uri没有被excludePatterns匹配上,并且includePatterns为空,被拦截器拦截
  • uri没有被excludePatterns匹配,但是被includePatterns匹配,被拦截器拦截
  • uri没有被excludePatterns匹配,也没有被includePatterns匹配,不被拦截器拦截。

        关于匹配算法,有兴趣的小伙伴可以自行去研究一下AntPathMatcher的match方法。

如果没有找到对应的 Handler 的错误处理

        每个请求都会对应一个 Handler,因为每个请求都会在后台有相应的逻辑对应,而逻辑的实现就是在 Handler 中,所以一旦遇到没有找到的 Handler 的情况(正常情况下如果没有 URL 匹配的 Handler),开发人员可以设置默认的 Handler 来处理相关请求,但是如果默认的请求也未设置就会出现 Handler为空的情况,就只能通过 response 向用户返回错误信息了。

protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
	if (pageNotFoundLogger.isWarnEnabled()) {
		pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + getRequestUri(request) +
				"] in DispatcherServlet with name '" + getServletName() + "'");
	}
	if (this.throwExceptionIfNoHandlerFound) {
		ServletServerHttpRequest sshr = new ServletServerHttpRequest(request);
		throw new NoHandlerFoundException(
				sshr.getMethod().name(), sshr.getServletRequest().getRequestURI(), sshr.getHeaders());
	}
	else {
		response.sendError(HttpServletResponse.SC_NOT_FOUND);
	}
}

根据当前 Handler 寻找对应的 HandlerAdapter

        在 WebApplicationContext 的初始化过程中我们讨论了 HandlerAdapters 的初始化,了解了默认情况下普通web 请求会交给 SimpleControllerHandlerAdapter 去处理,下面我们以 SimpleControllerHandlerAdapter 为例来分析获取适配器的逻辑。

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
	for (HandlerAdapter ha : this.handlerAdapters) {
		if (logger.isTraceEnabled()) {
			logger.trace("Testing handler adapter [" + ha + "]");
		}
		if (ha.supports(handler)) {
			return ha;
		}
	}
	throw new ServletException("No adapter for handler [" + handler +
			"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

        通过上面的函数中我们了解到,对于获取适配器的逻辑无非是遍历所有的适配器来选择合适的适配器并返回它,而某个适配器是否适用于当前的 Handler 逻辑被封装在具体的适配器中,进一步查看 SimpleControllerHandlerAdapter 的 supports 方法。

public boolean supports(Object handler) {
        return (handler instanceof Controller);
}

        分析到这里,一切都明了了,SimpleControllerHandlerAdapter 就是用于处理普通的 Web请求的,而且对于 Spring MVC 来说,我们会把逻辑封装至 Controller 的子类中,例如我们之前的引导示例 UserController 就是继承自 AbstractController,而 AbstractController 实现了 Controller 接口。

缓存处理

        在研究 Spring 对缓存处理的功能支持前,我们先了解一个概念,Last-Modified 缓存机制。

  1. 客户端在第一次输入 URL 时,服务器端会返回内容和状态码200,表示请求成功,同时会添加一个"Last-Modified" 的响应头,表示此文件在服务器上最后更新时间,例如"Last-Modified:Web,14 Mar 2020 10:22:42 GMT" 表示最后更新时间为(2020-05-14 10:22:42)
  2. 客户端第二次请求此 URL 时,客户端会向服务器发送请求头"If-Modified-Since",询问服务器该时间之后当前请求内容是否有被修改过,如果"if-Modified-Since:Web,14 Mar 2020 10:22:43 GMT" ,如果服务器内容没有变化,则自动返回 HTTP 304 状态码(只要响应头内容为空,这样就节省了网络的带宽)

        Spring 提供了对 Last-Modified 机制的支持,只需要实现 LastModified接口,如下图所示:

public class HelloWorldLastModifiedCacheController extends AbstractController implements LastModified {
    private long lastModified;

    @Override
    protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
        System.out.println(" cache modified 请求");
        response.getWriter().write("<a href=''>this</a>");
        return null;
    }

    @Override
    public long getLastModified(HttpServletRequest request) {
        if(lastModified == 0l){
            // 第一次或者逻辑变化的时候,应该重新的返回内容最新的修改时间戳
            lastModified = System.currentTimeMillis();
        }
        return lastModified;
    }
}

        HelloWorldLastModifiedCacheController只需要实现 LastModified 接口的 getLastModified 方法,保证当内容发生改变时返回最新修改的时间即可。
        Spring 判断是否过期是通过判断请求"If-Modified-Since"是否大于等于当前的 GetLastModified 方法返回的时间戳,如果是,则认为没有被修改,上面的 Controller 与普通的 controller 并没有什么太大的差别。声明如下:

        <bean name=“helloWorldLastModifiedCacheController” class=“com.spring_1_100.test_71_80.test75_springmvc.HelloWorldLastModifiedCacheController”></bean>

HandlerInterceptor 的处理

        Servlet API 定义的 servlet 过滤器可以在 servlet 处理的每个 web 请求后分别对它进行前置处理和后置处理,此外,有些时候,你可能只想处理由某些 Spring MVC 处理程序处理的 Web 请求,并在这些处理程序返回的模型属性被传递到视图之前,对它们进行一些操作。
        Spring MVC 允许你通过处理拦截器 Web 请求,进行前置处理和后置处理,处理拦截器是在 Spring的 Web 应用程序上下文配置的,因此它们可以利用各种容器特性,并引用容器中声明的 bean,处理拦截器针对特殊处理程序映射进行注册的。因此它只拦截通过这些处理程序映射的请求,每个处理拦截都必需实现 HandlerInterceptor接口,它包含三个需要你实现的回调方法,preHandle(),postHandle()和 afterCompletion(),第一个和第二个方法分别是处理请求之前和之后被调用,第二个方法还允许访问返回的 ModelAndView 对象,因此,可以在它里面操作模型属性,最后一个方法是在所有的请求处理完成后被调用的,如视图呈现之后,以下是 HandlerInterceptor 的简单实现。

MyTestInterceptor.java
public class MyTestInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        long startTime = System.currentTimeMillis();
        request.setAttribute("startTime",startTime);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        long startTime = (long)request.getAttribute("startTime");
        request.removeAttribute("startTime");
        long endTime = System.currentTimeMillis();
        System.out.println("request time :" + (endTime -startTime));
        modelAndView.addObject("handlingTime",endTime-startTime);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

        在这个拦截器的 preHandler()方法中,你记录了起始时间,并将它保存到请求属性中,这个方法应该返回了 true,允许 DispatcherServlet 继续处理请求,否则,DispatcherServlet 会认为这个方法己经处理了请求了,直接将响应返回给用户,然后在postHandler()方法中,从请求属性中加载起始时间,并将它与当前的时间进行比较,你可以 计算出总的持续时间,然后把这个时间添加到模型中,传递给视图,最后,afterCompletion()方法无事可做,空着就可以了。

逻辑处理

        对于逻辑处理其实是通过适配器中转调用 Handler 并返回视图的,对应的代码如下:
        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
        同样,还是以引导示例为基础进行处理逻辑分析,之前分析过,对于普通的 Web 请求,Spring 默认是使用 SimpleControllerHandleAdapter 类进行处理的,我们进入 SimpleControllerHandlerAdapter类的 Handler 方法如下:

SimpleControllerHandlerAdapter.java
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception {

    return ((Controller) handler).handleRequest(request, response);
}

        但是回顾示例中的 UserController,我们的逻辑是写在 handleRequestInternal 函数中而不是 handleRequest 函数中,所以我们还需要进一步分析期间所包含的处理流程。

AbstractController.java
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
        throws Exception {

    // Delegate to WebContentGenerator for checking and preparing.
    checkRequest(request);
    prepareResponse(response);

    //如果需要 session 同步
    if (this.synchronizeOnSession) {
        HttpSession session = request.getSession(false);
        if (session != null) {
            Object mutex = WebUtils.getSessionMutex(session);
            synchronized (mutex) {
            	//调用用户的逻辑
                return handleRequestInternal(request, response);
            }
        }
    }
	//调用用户的逻辑
    return handleRequestInternal(request, response);
}

异常视图的处理

        有时候系统运行过程中出现异常,而我们并不希望就此中断对用户的服务,而是至少告知客户当前系统在逻辑的过程中出现了异常,甚至告知他们因为什么原因导致的,Spring中的异常处理机制会帮我们完成这个事情,其实,在 Spring 主要的工作就是将逻辑引导至 HandlerExceptionResolver 类的 resolveException 方法,而 HandlerExceptionResolver 的使用,我们在讲解 WebApplicationContext 的初始化的时候己经介绍过了。

protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
		Object handler, Exception ex) throws Exception {

	// Check registered HandlerExceptionResolvers...
	ModelAndView exMv = null;
	for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
		exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
		if (exMv != null) {
			break;
		}
	}
	if (exMv != null) {
		if (exMv.isEmpty()) {
			request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
			return null;
		}
		// We might still need view name translation for a plain error model...
		if (!exMv.hasView()) {
			exMv.setViewName(getDefaultViewName(request));
		}
		if (logger.isDebugEnabled()) {
			logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex);
		}
		WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
		return exMv;
	}
	throw ex;
}
根据视图跳转页面

        无论是一个系统还是一个站点,最重要的工作还是与用户进行交互,用户操作系统后无论是下发命令成功是否都需要给用户一个反馈,以便于用户进行下一步的判断,所以逻辑处理最后一定会涉及一个页面跳转问题。

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
	// Determine locale for request and apply it to the response.
	Locale locale = this.localeResolver.resolveLocale(request);
	response.setLocale(locale);

	View view;
	if (mv.isReference()) {
		//We need to resolve the view name.
		view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
		if (view == null) {
			throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
					"' in servlet with name '" + getServletName() + "'");
		}
	}
	else {
		// No need to lookup: the ModelAndView object contains the actual View object.
		view = mv.getView();
		if (view == null) {
			throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
					"View object in servlet with name '" + getServletName() + "'");
		}
	}

	// Delegate to the View object for rendering.
	if (logger.isDebugEnabled()) {
		logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
	}
	try {
		view.render(mv.getModelInternal(), request, response);
	}
	catch (Exception ex) {
		if (logger.isDebugEnabled()) {
			logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +
					getServletName() + "'", ex);
		}
		throw ex;
	}
}
解析视图名称

        在上文中我们提到了 DispatcherServlet 会根据 ModelAndView 选择合适的视图来进行渲染,而这一功能就是在 resolveViewName 函数中完成。

protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
        HttpServletRequest request) throws Exception {
    for (ViewResolver viewResolver : this.viewResolvers) {
        View view = viewResolver.resolveViewName(viewName, locale);
        if (view != null) {
            return view;
        }
    }
    return null;
}

        我们以org.springframework.web.servlet.view.InternalResourceViewResolver为例来分析 ViewResolver 逻辑的解析过程,其中resolveViewName 函数的实现是其父类 AbstractCachingViewResolver 中完成的。

InternalResourceViewResolver.java
public View resolveViewName(String viewName, Locale locale) throws Exception {
    if (!isCache()) {
    	//不存在缓存的情况下直接创建视图
        return createView(viewName, locale);
    }
    else {
	    //直接从缓存中提取
        Object cacheKey = getCacheKey(viewName, locale);
        View view = this.viewAccessCache.get(cacheKey);
        if (view == null) {
            synchronized (this.viewCreationCache) {
                view = this.viewCreationCache.get(cacheKey);
                if (view == null) {
                    // Ask the subclass to create the View object.
                    view = createView(viewName, locale);
                    if (view == null && this.cacheUnresolved) {
                        view = UNRESOLVED_VIEW;
                    }
                    if (view != null) {
                        this.viewAccessCache.put(cacheKey, view);
                        this.viewCreationCache.put(cacheKey, view);
                        if (logger.isTraceEnabled()) {
                            logger.trace("Cached view [" + cacheKey + "]");
                        }
                    }
                }
            }
        }
        return (view != UNRESOLVED_VIEW ? view : null);
    }
}

        在父类 UrlBasedViewResolver 中重写了 createView 函数。

protected View createView(String viewName, Locale locale) throws Exception {
    //如果当前解析器不支持当前解析器如 viewName 为空等情况
    if (!canHandle(viewName, locale)) {
        return null;
    }
    //处理前缀为 redirect:xxx 的情况
    if (viewName.startsWith("redirect:")) {
        String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
        RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
        return applyLifecycleMethods(viewName, view);
    }
    //处理前缀为forward:xxx 的情况
    if (viewName.startsWith("forward:")) {
        String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
        return new InternalResourceView(forwardUrl);
    }
    // Else fall back to superclass implementation: calling loadView.
    return super.createView(viewName, locale);
}
protected View createView(String viewName, Locale locale) throws Exception {
    return loadView(viewName, locale);
}
protected View loadView(String viewName, Locale locale) throws Exception {
    AbstractUrlBasedView view = buildView(viewName);
    View result = applyLifecycleMethods(viewName, view);
    return (view.checkResource(locale) ? result : null);
}
UrlBasedViewResolver.java
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
    AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass());
    //添加前缀及后缀
    view.setUrl(getPrefix() + viewName + getSuffix());

    String contentType = getContentType();
    if (contentType != null) {
    	//设置ContextType
        view.setContentType(contentType);
    }

    view.setRequestContextAttribute(getRequestContextAttribute());
    view.setAttributesMap(getAttributesMap());

    Boolean exposePathVariables = getExposePathVariables();
    if (exposePathVariables != null) {
        view.setExposePathVariables(exposePathVariables);
    }
    Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
    if (exposeContextBeansAsAttributes != null) {
        view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
    }
    String[] exposedContextBeanNames = getExposedContextBeanNames();
    if (exposedContextBeanNames != null) {
        view.setExposedContextBeanNames(exposedContextBeanNames);
    }

    return view;
}

        通读以上的代码,我们发现对于 InternalResourceViewResolver 所提供的功能主要考虑到以下的几个方面处理。

  • 基于效率的考虑,提供了缓存的支持。
  • 提供了对 redirect:xxx 和 forward:xxx 前缀的支持。
  • 添加了前缀及后缀,并向 View 中加入了必需的属性设置。
页面跳转

        当通过 viewName 解析对对应的 View 后,就可以进一步的处理跳转逻辑了。

AbstractView.java
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
    if (logger.isTraceEnabled()) {
        logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +
            " and static attributes " + this.staticAttributes);
    }

    Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
    prepareResponse(request, response);
    renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}

        在引导示例中,我们了解对于 ModelView的使用,可以将一些属性直接放入其中,然后在页面直接通过 JSTL语法或者原始的 request 获取,这是一个很方便的也是很神奇的功能,但是实现并不是很复杂,无非是把我们要用到的属性放入到 request 中,以便在其他的地方可以直接调用,而解析的这些属性的工作就是在 createMergedOutModel 函数中完成。

InternalResourceView.java
protected Map<String, Object> createMergedOutputModel(Map<String, ?> model, HttpServletRequest request,
        HttpServletResponse response) {

    @SuppressWarnings("unchecked")
    Map<String, Object> pathVars = (this.exposePathVariables ?
            (Map<String, Object>) request.getAttribute(View.PATH_VARIABLES) : null);

    // Consolidate static and dynamic model attributes.
    int size = this.staticAttributes.size();
    size += (model != null ? model.size() : 0);
    size += (pathVars != null ? pathVars.size() : 0);

    Map<String, Object> mergedModel = new LinkedHashMap<String, Object>(size);
    mergedModel.putAll(this.staticAttributes);
    if (pathVars != null) {
        mergedModel.putAll(pathVars);
    }
    if (model != null) {
        mergedModel.putAll(model);
    }
    
    // Expose RequestContext?
    if (this.requestContextAttribute != null) {
        mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel));
    }

    return mergedModel;
}
InternalResourceView.java
protected void renderMergedOutputModel(
        Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

    //将 model 中的数据以属性的方式设置到 request中
    exposeModelAsRequestAttributes(model, request);

    // Expose helpers as request attributes, if any.
    exposeHelpers(request);

    // Determine the path for the request dispatcher.
    String dispatcherPath = prepareForRendering(request, response);

    // Obtain a RequestDispatcher for the target resource (typically a JSP).
    RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
    if (rd == null) {
        throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
                "]: Check that the corresponding file exists within your web application archive!");
    }

    // If already included or response already committed, perform include, else forward.
    if (useInclude(request, response)) {
        response.setContentType(getContentType());
        if (logger.isDebugEnabled()) {
            logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
        }
        rd.include(request, response);
    }

    else {
        // Note: The forwarded resource is supposed to determine the content type itself.
        if (logger.isDebugEnabled()) {
            logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
        }
        rd.forward(request, response);
    }
}
InternalResourceView.java
protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {
    for (Map.Entry<String, Object> entry : model.entrySet()) {
        String modelName = entry.getKey();
        Object modelValue = entry.getValue();
        if (modelValue != null) {
            request.setAttribute(modelName, modelValue);
            if (logger.isDebugEnabled()) {
                logger.debug("Added model object '" + modelName + "' of type [" + modelValue.getClass().getName() +
                        "] to request in view with name '" + getBeanName() + "'");
            }
        }
        else {
            request.removeAttribute(modelName);
            if (logger.isDebugEnabled()) {
                logger.debug("Removed model object '" + modelName +
                        "' from request in view with name '" + getBeanName() + "'");
            }
        }
    }
}

        源码分析到这里,我相信大家对 Spring MVC 原理及源码有了一定的理解了,下面我们来总结关于 Spring MVC 代码的整个流程。

流程
1) contextInitialized:该方法在 ServletContext 启动后被调用,并准备好处理客户端请求
	1)initWebApplicationContext:初始化WebApplicationContext
		1)createWebApplicationContext:创建 WebApplicationContext
		2)configureAndRefreshWebApplicationContext:设置并刷新 ApplicationContext 容器
			1)setId():设置 id
			2)setServletContext():设置 servletContext
			3)setConfigLocation():设置configLocation
			4)initPropertySources():初始化属性资源
				1)replace(servletContextInitParams):替换掉环境中的servletContextInitParams
				2)replace(servletConfigInitParams):替换掉环境变量中的servletConfigInitParams
			5)customizeContext():加载servlet 中的所有contextInitializerClasses和globalInitializerClasses并调用他的initialize方法。
			6)refresh:调用容器的刷新方法,初始化容器
2)init(): ServletContext 启动后调用DispatcherServlet初始化方法
	1)ServletConfigPropertyValues():解析init-parm并封装至pvs中
		1)registerCustomEditor():注册资源编辑器
		2)initBeanWrapper():预留给子类 
		3)initServletBean():初始化 serviceBean
			1)initWebApplicationContext():初始化WebApplicationContext
				1)setParent():设置父容器
				2)configureAndRefreshWebApplicationContext():配置并刷新ApplicationContext 容器
					1)setId():设置 id
					2)setServletContext():设置 Servlet容器
					3)setServletConfig():设置 setvlet 配置
					4)setNamespace():设置命名空间
					5)addApplicationListener():添加 Application 监听器ContextRefreshListener
					6)initPropertySources():初始化属性资源
					7)postProcessWebApplicationContext():预留给子类 
					8)applyInitializers():调用配置的所有的contextInitializerClasses的initialize方法。
					9)refresh():刷新容器
						1)finishRefresh():刷新完成的相关处理
							1)initStrategies():初始化Servlet 容器相关策略
								1)initMultipartResolver(context):初始化MultipartResolver
								2)initLocaleResolver(context):初始化LocaleResolver
								3)initThemeResolver(context):初始化ThemeResolver
								4)initHandlerMappings(context):初始化HandlerMappings
								5)initHandlerAdapters(context):初始化HandlerAdapters
								6)initHandlerExceptionResolvers(context):初始化HandlerExceptionResolvers
								7)initRequestToViewNameTranslator(context):初始化RequestToViewNameTranslator
								8)initViewResolvers(context):初始化ViewResolvers
								9)initFlashMapManager(context):初始化FlashMapManager

3)doGet/doPost:Servlet 的GET 或 POST方法
	1)processRequest(request, response):处理 request 请求。
		1)doService():开始处理
			1)doDispatch():do 处理
				1)checkMultipart():如果是文件上传,将request 转化为MultipartParsingResult
				2)getHandler():获取 uri 处理器
				3)noHandlerFound():无处理器,直接终止
				5)getHandlerAdapter():将 handler 转化为HandlerAdapter
				6)applyPreHandle():调用拦截器链的所有preHandle 方法。
				7)handle():调用直在的业务逻辑
					1)handleRequest():处理 request 请求。
						1)handleRequestInternal():调用实际业务逻辑
				8)applyDefaultViewName():如果当前请求没有对应的视图,设置默认视图
				9)applyPostHandle():调用拦截器链的postHandle方法
				10)processDispatchResult:结果调度
					1)processHandlerException:异常结果调度
					2)render():render 视图
					3)triggerAfterCompletion():调用拦截器链的afterCompletion方法。
				11)triggerAfterCompletion():如果抛出异常,调用拦截器链的afterCompletion方法。
				12)triggerAfterCompletionWithError():如果抛出 Error,调用拦截器链的afterCompletion方法。
				13)applyAfterConcurrentHandlingStarted():异步请求处理
				14)cleanupMultipart:如果是文件上传,删除临时文件
思考?

        相信大家对 SpringMVC 的源码有了一定的理解了,我们来看一个例子。

1.创建 Controller
//处理登录请求的后端控制器

//注意:@RequestParam注解中的required注解对表单提交中的属性是没有用的,就算不填它也会默认为空字符串,它只对GET请求中
//在url后加的key-value的属性有限制作用

@Controller
@RequestMapping(value = {"/test"})
public class UserController {

    //如果是GET方法请求的话,就直接给用户返回登录的页面,此页面表单请求的方法为POST
    @RequestMapping(value = {"/login"},method = {RequestMethod.GET})
    public ModelAndView LoginGet(){
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("index");
        return modelAndView;
    }

    private static final String CURRENT_USER = "Now_user";
    // http://localhost:8080/test/login.htm?userName=zhangsan&password=lizi
    @RequestMapping(value = {"login"},method = {RequestMethod.POST})
    //让请求的url后面必须跟上一个叫做userName的属性,是用户的用户名
    public ModelAndView LoginPost(@RequestParam(value = "userName") String userName,
                                  //请求的url后必须跟上password属性,为用户当前的密码
                                  @RequestParam(value = "password") String password,
                                  //Spring MVC框架集成了Servlet请求响应等一系列参数,可以在有需要的时候使用
                                  HttpServletRequest request, HttpServletResponse response,
                                  HttpSession session, RedirectAttributes redirectAttributes) {

        //这里是和后端交互的代码,如果是用户登录的话就在数据库中查找对应的用户信息
        if (userName.isEmpty() || password.isEmpty()) {
            ModelAndView modelAndView = new ModelAndView();
            modelAndView.addObject("error", "用户名或密码为空");
            modelAndView.setViewName("index");
            return modelAndView;
        }


        //到了这里就说明用户登录成功了
        System.out.println("===========================:" + userName + "密码是:" + password);
        //使用session进行会话跟踪
        session.setAttribute(CURRENT_USER, userName);

        //创建模型与视图类,返回给前端控制器
        ModelAndView modelAndView = new ModelAndView();

        //重定向的时候,因为是客户端重新的请求,所以参数是不会被传到重定向页面的
        //所以使用此方法,可以把属性放到一个叫做FlashMap的Map中
        redirectAttributes.addFlashAttribute("userName", userName);
        redirectAttributes.addFlashAttribute("password", password);


        redirectAttributes.addAttribute("uname", userName);
        redirectAttributes.addAttribute("pwd", password);

        //使用重定向的时候不能写jsp的名字,要写url映射的路径
        modelAndView.setViewName("redirect:/test/main");
        return modelAndView;
    }

}
@Controller
@RequestMapping(value = {"/test"})
public class MainController {

    @RequestMapping(value = {"/main"}, method = {RequestMethod.GET})
    public ModelAndView GetMain(
            HttpServletRequest request, HttpSession session) {

        ModelAndView modelAndView = new ModelAndView();

        //对FlashMap中的参数进行提取,有两种方法
        //第一种,使用RequestContextUtils(请求工具包),因为在重定向后FlashMap会把表单中的属性
        //放在重定向新的请求中,所以可以获得请求中的FlashMap
        Map<String, ?> map = RequestContextUtils.getInputFlashMap(request);
        //把FlashMap直接放入模型,传给前端控制器
        modelAndView.addAllObjects(map);
        //视图名传入
        modelAndView.setViewName("test/main");
        System.out.println("==========================get ");

        return modelAndView;

    }


    //第二种:使用@ModelAttribute注解
    //因为FlashMap是处理这个url的初始化数据模型,所以可以通过这个注解拿到FlashMap的属性
    @RequestMapping(value = {"/main"}, method = {RequestMethod.POST})
    public String PostMain(@ModelAttribute(value = "userName") String userName,
                           @ModelAttribute(value = "password") String password) {
        return "test/main";
    }
}
2.创建web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:web="http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee">
    <display-name>spring_tiny</display-name>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring_101_200/config_181_190/spring185.xml</param-value>
    </context-param>

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

    <servlet-mapping>
        <servlet-name>spring_tiny</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

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

    <!-- 自己配置描述文件,需要多少个描述文件就配置多少 -->
    <jsp-config>
        <!-- 配置c描述文件-对应c标签,这里的taglib-uri对应jsp中引入的uri -->
        <taglib>
            <taglib-uri>http://www.codecoord.com</taglib-uri>
            <taglib-location>/WEB-INF/c.tld</taglib-location>
        </taglib>
    </jsp-config>
</web-app>
3.创建 Spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <mvc:annotation-driven />
    <context:component-scan base-package="com.spring_101_200.test_181_190.test_185_spring_mvc"></context:component-scan>
    
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>
</beans>
4.创建 index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <meta charset="UTF-8">
    <title>主界面</title>
</head>
<body>

<script>
    function Logout() {
        <%
        session.invalidate();
        %>
    }
</script>
<form id="login" name="login" method ="post"  action="/test/login.htm">
    <p>用户名:<input id="userName" name="userName" type="text" /></p>  <!--用户名文本框-->
    <p>密 码:<input id="password" name="password" type="text" /></p>                     <!--密码文本框-->
    <p><button id="subLogin"  name ="subLogin" type="submit" value="提交" >提交</button></p><!--提交按钮-->
</form>

</body>
</html>
5.创建 main.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <meta charset="UTF-8">
    <title>主界面</title>
</head>
<body>

<script>
    function Logout() {
        <%
        session.invalidate();
        %>
    }
</script>

<p>登录成功</p>
你的用户名为:<p>${userName}</p>
你的密码为:<p>${password}</p>
<a href="login" οnclick="Logout">退出</a>
</body>
</html>
登陆介面

在这里插入图片描述

重定向介面

在这里插入图片描述
        聪明的读者,你觉得 Spring源码对于上例是如何实现的呢?

本文的github地址是
https://github.com/quyixiao/spring_tiny/tree/master/src/main/java/com/spring_1_100/test_71_80/test75_springmvc

  • 0
    点赞
  • 2
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值