看透 Spring MVC 源代码分析与实践 —— 俯视 Spring MVC

看透 Spring MVC 源代码分析与实践 —— 俯视 Spring MVC

友情提示:欢迎关注公众号【芋道源码】。😈关注后,拉你进【源码圈】微信群讨论技术和源码。
友情提示:欢迎关注公众号【芋道源码】。😈关注后,拉你进【源码圈】微信群讨论技术和源码。
友情提示:欢迎关注公众号【芋道源码】。😈关注后,拉你进【源码圈】微信群讨论技术和源码。

Spring MVC 之初体验

环境搭建

在 IDEA 中新建一个 web 项目,用 Maven 管理项目的话,在 pom.xml 中加入 Spring MVC 和 Servlet 依赖即可。

  1. <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->

  2. <dependency>

  3.    <groupId>org.springframework</groupId>

  4.    <artifactId>spring-webmvc</artifactId>

  5.    <version>4.3.9.RELEASE</version>

  6. </dependency>

  7. <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->

  8. <dependency>

  9.    <groupId>javax.servlet</groupId>

  10.    <artifactId>javax.servlet-api</artifactId>

  11.    <version>3.1.0</version>

  12.    <scope>provided</scope>

  13. </dependency>

Spring MVC 简单配置

  • 在 web.xml 中配置 Servlet

  • 创建 Spring MVC 的 xml 配置文件

  • 创建 Controller 和 View

1、web.xml

  1. <!-- Spring MVC配置 -->

  2. <!-- ====================================== -->

  3. <servlet>

  4.    <servlet-name>spring</servlet-name>

  5.    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

  6.    <!-- 可以自定义servlet.xml配置文件的位置和名称,默认为WEB-INF目录下,名称为[<servlet-name>]-servlet.xml,如spring-servlet.xml

  7.    <init-param>

  8.        <param-name>contextConfigLocation</param-name>

  9.        <param-value>/WEB-INF/spring-servlet.xml</param-value>&nbsp; 默认

  10.    </init-param>

  11.    -->

  12.    <load-on-startup>1</load-on-startup>

  13. </servlet>

  14. <servlet-mapping>

  15.    <servlet-name>spring</servlet-name>

  16.    <url-pattern>*.do</url-pattern>

  17. </servlet-mapping>

  18. <!-- Spring配置 -->

  19. <!-- ====================================== -->

  20. <listener>

  21.   <listenerclass>

  22.     org.springframework.web.context.ContextLoaderListener

  23.   </listener-class>

  24. </listener>

  25. <!-- 指定Spring Bean的配置文件所在目录。默认配置在WEB-INF目录下 -->

  26. <context-param>

  27.    <param-name>contextConfigLocation</param-name>

  28.    <param-value>classpath:config/applicationContext.xml</param-value>

  29. </context-param>

2、spring-servlet.xml

  1. <?xml version="1.0" encoding="UTF-8"?>

  2. <beans xmlns="http://www.springframework.org/schema/beans"

  3.       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"

  4.        xmlns:context="http://www.springframework.org/schema/context"

  5.   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

  6.       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd

  7.       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd

  8.       http://www.springframework.org/schema/context <a href="http://www.springframework.org/schema/context/spring-context-3.0.xsd">http://www.springframework.org/schema/context/spring-context-3.0.xsd</a>">

  9.    <!-- 启用spring mvc 注解 -->

  10.    <context:annotation-config />

  11.    <!-- 设置使用注解的类所在的jar包 -->

  12.    <context:component-scan base-package="controller"></context:component-scan>

  13.    <!-- 完成请求和注解POJO的映射 -->

  14.    <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />

  15.   

  16.    <!-- 对转向页面的路径解析。prefix:前缀, suffix:后缀 -->

  17.    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="/jsp/" p:suffix=".jsp" />

  18. </beans>

3、Controller

  1. package controller;

  2. import javax.servlet.http.HttpServletRequest;

  3. import org.springframework.stereotype.Controller;

  4. import org.springframework.web.bind.annotation.RequestMapping;

  5. import org.springframework.web.bind.annotation.RequestParam;

  6. import entity.User;

  7. @Controller  //类似Struts的Action

  8. public class TestController {

  9.    @RequestMapping("/test/login.do")  // 请求url地址映射,类似Struts的action-mapping

  10.    public String testLogin(@RequestParam(value="username")String username, String password, HttpServletRequest request) {

  11.        // @RequestParam是指请求url地址映射中必须含有的参数(除非属性 required=false, 默认为 true)

  12.        // @RequestParam可简写为:@RequestParam("username")

  13.        if (!"admin".equals(username) || !"admin".equals(password)) {

  14.            return "loginError"; // 跳转页面路径(默认为转发),该路径不需要包含spring-servlet配置文件中配置的前缀和后缀

  15.        }

  16.        return "loginSuccess";

  17.    }

  18.    @RequestMapping("/test/login2.do")

  19.    public ModelAndView testLogin2(String username, String password, int age){

  20.        // request和response不必非要出现在方法中,如果用不上的话可以去掉

  21.        // 参数的名称是与页面控件的name相匹配,参数类型会自动被转换

  22.        if (!"admin".equals(username) || !"admin".equals(password) || age < 5) {

  23.            return new ModelAndView("loginError"); // 手动实例化ModelAndView完成跳转页面(转发),效果等同于上面的方法返回字符串

  24.        }

  25.        return new ModelAndView(new RedirectView("../index.jsp"));  // 采用重定向方式跳转页面

  26.        // 重定向还有一种简单写法

  27.        // return new ModelAndView("redirect:../index.jsp");

  28.    }

  29.    @RequestMapping("/test/login3.do")

  30.    public ModelAndView testLogin3(User user) {

  31.        // 同样支持参数为表单对象,类似于Struts的ActionForm,User不需要任何配置,直接写即可

  32.        String username = user.getUsername();

  33.        String password = user.getPassword();

  34.        int age = user.getAge();

  35.        if (!"admin".equals(username) || !"admin".equals(password) || age < 5) {

  36.            return new ModelAndView("loginError");

  37.        }

  38.        return new ModelAndView("loginSuccess");

  39.    }

  40.    @Resource(name = "loginService")  // 获取applicationContext.xml中bean的id为loginService的,并注入

  41.    private LoginService loginService;  //等价于spring传统注入方式写get和set方法,这样的好处是简洁工整,省去了不必要得代码

  42.    @RequestMapping("/test/login4.do")

  43.    public String testLogin4(User user) {

  44.        if (loginService.login(user) == false) {

  45.            return "loginError";

  46.        }

  47.        return "loginSuccess";

  48.    }

  49. }

@RequestMapping 可以写在方法上,也可以写在类上,上面代码方法上的 RequestMapping 都含有 /test , 那么我们就可以将其抽出直接写在类上,那么方法里面就不需要写 /test 了。

如下即可:

  1. @Controller

  2. @RequestMapping("/test")

  3. public class TestController {

  4.    @RequestMapping("/login.do")  // 请求url地址映射,类似Struts的action-mapping

  5.    public String testLogin(@RequestParam(value="username")String username, String password, HttpServletRequest request) {

  6.        // @RequestParam是指请求url地址映射中必须含有的参数(除非属性 required=false, 默认为 true)

  7.        // @RequestParam可简写为:@RequestParam("username")

  8.        if (!"admin".equals(username) || !"admin".equals(password)) {

  9.            return "loginError"; // 跳转页面路径(默认为转发),该路径不需要包含spring-servlet配置文件中配置的前缀和后缀

  10.        }

  11.        return "loginSuccess";

  12.    }

  13.    //省略其他的

  14. }

上面的代码方法的参数中可以看到有一个 @RequestParam 注解,其实还有 @PathVariable 。这两个的区别是啥呢?

  • @PathVariable 标记在方法的参数上,利用它标记的参数可以利用请求路径传值。

  • @RequestParam是指请求url地址映射中必须含有的参数(除非属性 required=false, 默认为 true)

看如下例子:

  1. @RequestMapping("/user/{userId}")  // 请求url地址映射

  2. public String userinfo(Model model, @PathVariable("userId") int userId,  HttpSession session) {

  3.         System.out.println("进入  userinfo  页面");

  4.        //判断是否有用户登录

  5.        User user1 = (User) session.getAttribute("user");

  6.        if (user1 == null) {

  7.            return "login";

  8.        }

  9.        User user = userService.selectUserById(userId);

  10.        model.addAttribute("user", user);

  11.        return "userinfo";

  12.    }

上面例子中如果浏览器请求的是 /user/1 的时候,就表示此时的用户 id 为 1,此时就会先从 session 中查找是否有 “user” 属性,如果有的话,就代表用户此时处于登录的状态,如果没有的话,就会让用户返回到登录页面,这种机制在各种网站经常会使用的,然后根据这个 id = 1 ,去查找用户的信息,然后把查找的 “user” 放在 model 中,然后返回用户详情页面,最后在页面中用 $!{user.name} 获取用户的名字,同样的方式可以获取用户的其他信息,把所有的用户详情信息展示出来。

创建 Spring MVC 之器

Spring MVC 核心 Servlet 架构图如下:

看透 Spring MVC 源代码分析与实践 —— 俯视 Spring MVC

Java 中常用的 Servlet 我在另外一篇文章写的很清楚了,有兴趣的请看:通过源码详解 Servlet ,这里我就不再解释了。

这里主要讲 Spring 中的 HttpServletBean、FrameworkServlet、DispatcherServlet 这三个类的创建过程。

通过上面的图,可以看到这三个类直接实现三个接口:EnvironmentCapable、EnvironmentAware、ApplicationContextAware。下面我们直接看下这三个接口的内部是怎样写的。

EnvironmentCapable.java

  1. public interface EnvironmentCapable {

  2.    //返回组件的环境,可能返回 null 或者默认环境

  3.    @Nullable

  4.    Environment getEnvironment();

  5. }

EnvironmentAware.java

  1. public interface EnvironmentAware extends Aware {

  2.    //设置组件的运行环境

  3.    void setEnvironment(Environment environment);

  4. }

ApplicationContextAware.java

  1. public interface ApplicationContextAware extends Aware {

  2.    //设置运行对象的应用上下文

  3.    //当类实现这个接口后,这个类可以获取ApplicationContext中所有的bean,也就是说这个类可以直接获取Spring配置文件中所有有引用到的bean对象

  4.    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;

  5. }

怎么使用这个这个接口呢?

参考文章:org.springframework.context.ApplicationContextAware使用理解

HttpServletBean

这里就直接看其中最重要的 init() 方法的代码了:

  1. /**

  2. * 将配置参数映射到此servlet的bean属性,并调用子类初始化。

  3. *  如果 bean 配置不合法(或者需要的参数丢失)或者子类初始化发生错误,那么就会抛出 ServletException 异常

  4. */

  5. @Override

  6. public final void init() throws ServletException {

  7.   //日志代码删除了

  8.   // 从init参数设置bean属性。

  9.   //获得web.xml中的contextConfigLocation配置属性,就是spring MVC的配置文件

  10.   PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);

  11.   if (!pvs.isEmpty()) {

  12.      try {

  13.         BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);

  14.         //获取服务器的各种信息

  15.         ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());

  16.         bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));

  17.         //模板方法,可以在子类中调用,做一些初始化工作,bw代表DispatcherServelt

  18.         initBeanWrapper(bw);

  19.         //将配置的初始化值设置到DispatcherServlet中

  20.         bw.setPropertyValues(pvs, true);

  21.      }

  22.      catch (BeansException ex) {

  23.         //日志代码

  24.         throw ex;

  25.      }

  26.   }

  27.   // Let subclasses do whatever initialization they like.

  28.   //模板方法,子类初始化的入口方法

  29.   initServletBean();

  30.   //日志代码删除了

  31. }

FrameworkServlet

其中重要方法如下:里面也就两句关键代码,日志代码我直接删掉了

  1. protected final void initServletBean() throws ServletException {

  2.        //日志代码删除了

  3.        long startTime = System.currentTimeMillis();

  4.        //就是 try 语句里面有两句关键代码

  5.        try {

  6.            //初始化 webApplicationContext

  7.            this.webApplicationContext = initWebApplicationContext();

  8.            //模板方法,

  9.            initFrameworkServlet();

  10.        }

  11.        catch (ServletException ex) {

  12.            this.logger.error("Context initialization failed", ex);

  13.            throw ex;

  14.        }

  15.        catch (RuntimeException ex) {

  16.            this.logger.error("Context initialization failed", ex);

  17.            throw ex;

  18.        }

  19.        //日志代码删除了

  20.    }

再来看看上面代码中调用的 initWebApplicationContext() 方法

  1. protected WebApplicationContext initWebApplicationContext() {

  2.        //获取 rootContext

  3.        WebApplicationContext rootContext =

  4.                WebApplicationContextUtils.getWebApplicationContext(getServletContext());

  5.        WebApplicationContext wac = null;

  6.        if (this.webApplicationContext != null) {

  7.            // 上下文实例在构造时注入 - >使用它

  8.            wac = this.webApplicationContext;

  9.            if (wac instanceof ConfigurableWebApplicationContext) {

  10.                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;

  11.                if (!cwac.isActive()) {

  12.                // 如果上下文尚未刷新 -> 提供诸如设置父上下文,设置应用程序上下文ID等服务

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

  14.            // 上下文实例被注入没有显式的父类 -> 将根应用程序上下文(如果有的话可能为null)设置为父级

  15.                        cwac.setParent(rootContext);

  16.                    }

  17.                    configureAndRefreshWebApplicationContext(cwac);

  18.                }

  19.            }

  20.        }

  21.        if (wac == null) {

  22.    // 当 WebApplicationContext 已经存在 ServletContext 中时,通过配置在 servlet 中的 ContextAttribute 参数获取

  23.            wac = findWebApplicationContext();

  24.        }

  25.        if (wac == null) {

  26.            // 如果 WebApplicationContext 还没有创建,则创建一个

  27.            wac = createWebApplicationContext(rootContext);

  28.        }

  29.        if (!this.refreshEventReceived) {

  30.            // 当 ContextRefreshedEvent 事件没有触发时调用此方法,模板方法,可以在子类重写

  31.            onRefresh(wac);

  32.        }

  33.        if (this.publishContext) {

  34.            // 将 ApplicationContext 保存到 ServletContext 中去

  35.            String attrName = getServletContextAttributeName();

  36.            getServletContext().setAttribute(attrName, wac);

  37.            if (this.logger.isDebugEnabled()) {

  38.                this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +

  39.                        "' as ServletContext attribute with name [" + attrName + "]");

  40.            }

  41.        }

  42.        return wac;

  43.    }

initWebApplicationContext 方法做了三件事:

  • 获取 Spring 的根容器 rootContext

  • 设置 webApplicationContext 并根据情况调用 onRefresh 方法

  • 将 webApplicationContext 设置到 ServletContext 中

这里在讲讲上面代码中的 wac == null 的几种情况:

1)、当 WebApplicationContext 已经存在 ServletContext 中时,通过配置在 servlet 中的 ContextAttribute 参数获取,调用的是 findWebApplicationContext() 方法

  1. protected WebApplicationContext findWebApplicationContext() {

  2.        String attrName = getContextAttribute();

  3.        if (attrName == null) {

  4.            return null;

  5.        }

  6.        WebApplicationContext wac =

  7.                WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);

  8.        if (wac == null) {

  9.            throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");

  10.        }

  11.        return wac;

  12.    }

2)、如果 WebApplicationContext 还没有创建,调用的是 createWebApplicationContext 方法

  1. protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {

  2.        //获取创建类型

  3.        Class<?> contextClass = getContextClass();

  4.        //删除了打印日志代码

  5.        //检查创建类型

  6.        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {

  7.            throw new ApplicationContextException(

  8.                    "Fatal initialization error in servlet with name '" + getServletName() +

  9.                    "': custom WebApplicationContext class [" + contextClass.getName() +

  10.                    "] is not of type ConfigurableWebApplicationContext");

  11.        }

  12.        //具体创建

  13.        ConfigurableWebApplicationContext wac =

  14.                (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

  15.        wac.setEnvironment(getEnvironment());

  16.        wac.setParent(parent);

  17.  //并设置的 contextConfigLocation 参数传给 wac,默认是 WEB-INFO/[ServletName]-Servlet.xml

  18.        wac.setConfigLocation(getContextConfigLocation());

  19.        //调用的是下面的方法

  20.        configureAndRefreshWebApplicationContext(wac);

  21.        return wac;

  22.    }

  23. protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {

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

  25.            // The application context id is still set to its original default value

  26.            // -> assign a more useful id based on available information

  27.            if (this.contextId != null) {

  28.                wac.setId(this.contextId);

  29.            }

  30.            else {

  31.                // Generate default id...

  32.                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +

  33.                        ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());

  34.            }

  35.        }

  36.        wac.setServletContext(getServletContext());

  37.        wac.setServletConfig(getServletConfig());

  38.        wac.setNamespace(getNamespace());

  39.        wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

  40.        // The wac environment's #initPropertySources will be called in any case when the context

  41.        // is refreshed; do it eagerly here to ensure servlet property sources are in place for

  42.        // use in any post-processing or initialization that occurs below prior to #refresh

  43.        ConfigurableEnvironment env = wac.getEnvironment();

  44.        if (env instanceof ConfigurableWebEnvironment) {

  45.            ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());

  46.        }

  47.        postProcessWebApplicationContext(wac);

  48.        applyInitializers(wac);

  49.        wac.refresh();

  50.    }

里面还有 doXXX() 方法,大家感兴趣的可以去看看。

DispatcherServlet

DispatcherServlet 继承自 FrameworkServlet,onRefresh 方法是 DispatcherServlet 的入口方法,在 initStrategies 方法中调用了 9 个初始化的方法。

看透 Spring MVC 源代码分析与实践 —— 俯视 Spring MVC

这里分析其中一个初始化方法:initLocaleResolver() 方法

  1. private void initLocaleResolver(ApplicationContext context) {

  2.        try {

  3.            //在 context 中获取

  4.            this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);

  5.            //删除了打印日志的代码

  6.        }

  7.        catch (NoSuchBeanDefinitionException ex) {

  8.            //使用默认的策略

  9.            this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);

  10.            //删除了打印日志的代码

  11.        }

  12.    }

查看默认策略代码:

  1. protected <T> T getDefaultStrategy(ApplicationContext context, Class<T> strategyInterface) {

  2.        //调用 getDefaultStrategies 方法

  3.        List<T> strategies = getDefaultStrategies(context, strategyInterface);

  4.        if (strategies.size() != 1) {

  5.            throw new BeanInitializationException(

  6.                    "DispatcherServlet needs exactly 1 strategy for interface [" + strategyInterface.getName() + "]");

  7.        }

  8.        return strategies.get(0);

  9.    }

  10.    /**

  11.     * Create a List of default strategy objects for the given strategy interface.

  12.     * <p>The default implementation uses the "DispatcherServlet.properties" file (in the same

  13.     * package as the DispatcherServlet class) to determine the class names. It instantiates

  14.     * the strategy objects through the context's BeanFactory.

  15.     */

  16.    @SuppressWarnings("unchecked")

  17.    protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {

  18.        String key = strategyInterface.getName();

  19.        //根据策略接口的名字从 defaultStrategies 获取所需策略的类型

  20.        String value = defaultStrategies.getProperty(key);

  21.        if (value != null) {

  22.            //如果有多个默认值的话,就以逗号分隔为数组

  23.            String[] classNames = StringUtils.commaDelimitedListToStringArray(value);

  24.            List<T> strategies = new ArrayList<>(classNames.length);

  25.            //按获取到的类型初始化策略

  26.            for (String className : classNames) {

  27.                try {

  28.                    Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());

  29.                    Object strategy = createDefaultStrategy(context, clazz);

  30.                    strategies.add((T) strategy);

  31.                }

  32.                catch (ClassNotFoundException ex) {

  33.                    throw new BeanInitializationException(

  34.                            "Could not find DispatcherServlet's default strategy class [" + className + "] for interface [" + key + "]", ex);

  35.                }

  36.                catch (LinkageError err) {

  37.                    throw new BeanInitializationException(

  38.                            "Error loading DispatcherServlet's default strategy class [" + className + "] for interface [" + key + "]: problem with class file or dependent class", err);

  39.                }

  40.            }

  41.            return strategies;

  42.        }

  43.        else {

  44.            return new LinkedList<>();

  45.        }

  46.    }

其他几个方法大概也类似,我就不再写了。

小结

主要讲了 Spring MVC 自身创建过程,分析了 Spring MVC 中 Servlet 的三个层次:HttpServletBean、FrameworkServlet 和 DispatcherServlet。HttpServletBean 继承自 Java 的 HttpServlet,其作用是将配置的参数设置到相应的属性上;FrameworkServlet 初始化了 WebApplicationContext;DispatcherServlet 初始化了自身的 9 个组件。

Spring MVC 之用

分析 Spring MVC 是怎么处理请求的。首先分析 HttpServletBean、FrameworkServlet 和 DispatcherServlet 这三个 Servlet 的处理过程,最后分析 doDispatcher 的结构。

HttpServletBean

参与了创建工作,并没有涉及请求的处理。

FrameworkServlet

在类中的 service() 、doGet()、doPost()、doPut()、doDelete()、doOptions()、doTrace() 这些方法中可以看到都调用了一个共同的方法 processRequest() ,它是类在处理请求中最核心的方法。

  1. protected final void processRequest(HttpServletRequest request, HttpServletResponse response)

  2.            throws ServletException, IOException {

  3.        long startTime = System.currentTimeMillis();

  4.        Throwable failureCause = null;

  5.        //获取 LocaleContextHolder 中原来保存的 LocaleContext

  6.        LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();

  7.        //获取当前请求的 LocaleContext

  8.        LocaleContext localeContext = buildLocaleContext(request);

  9.        //获取 RequestContextHolder 中原来保存的 RequestAttributes

  10.        RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();

  11.        //获取当前请求的 ServletRequestAttributes

  12.        ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

  13.        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

  14.        asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

  15. //将当前请求的 LocaleContext 和 ServletRequestAttributes 设置到 LocaleContextHolder 和 RequestContextHolder

  16.        initContextHolders(request, localeContext, requestAttributes);

  17.        try {

  18.            //实际处理请求的入口,这是一个模板方法,在 Dispatcher 类中才有具体实现

  19.            doService(request, response);

  20.        }catch (ServletException ex) {

  21.            failureCause = ex;

  22.            throw ex;

  23.        }catch (IOException ex) {

  24.            failureCause = ex;

  25.            throw ex;

  26.        }catch (Throwable ex) {

  27.            failureCause = ex;

  28.            throw new NestedServletException("Request processing failed", ex);

  29.        }finally {

  30.            //将 previousLocaleContext,previousAttributes 恢复到 LocaleContextHolder 和 RequestContextHolder 中

  31.            resetContextHolders(request, previousLocaleContext, previousAttributes);

  32.            if (requestAttributes != null) {

  33.                requestAttributes.requestCompleted();

  34.            }

  35.            //删除了日志打印代码

  36.            //发布了一个 ServletRequestHandledEvent 类型的消息

  37.            publishRequestHandledEvent(request, response, startTime, failureCause);

  38.        }

  39.    }

DispatcherServlet

上一章中其实还没把该类讲清楚,在这个类中,里面的智行处理的入口方法应该是 doService 方法,方法里面调用了 doDispatch 进行具体的处理,在调用 doDispatch 方法之前 doService 做了一些事情:首先判断是不是 include 请求,如果是则对 request 的 Attribute 做个快照备份,等 doDispatcher 处理完之后(如果不是异步调用且未完成)进行还原 ,在做完快照后又对 request 设置了一些属性。

  1. protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {

  2.        // Keep a snapshot of the request attributes in case of an include,

  3.        // to be able to restore the original attributes after the include.

  4.        Map<String, Object> attributesSnapshot = null;

  5.        if (WebUtils.isIncludeRequest(request)) {

  6.            attributesSnapshot = new HashMap<>();

  7.            Enumeration<?> attrNames = request.getAttributeNames();

  8.            while (attrNames.hasMoreElements()) {

  9.                String attrName = (String) attrNames.nextElement();

  10.                if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)){

  11.                    attributesSnapshot.put(attrName, request.getAttribute(attrName));

  12.                }

  13.            }

  14.        }

  15.        // Make framework objects available to handlers and view objects.

  16.        request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());

  17.        request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);

  18.        request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);

  19.        request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

  20.        FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);

  21.        if (inputFlashMap != null) {

  22.            request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));

  23.        }

  24.        request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());

  25.        request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);

  26.        try {

  27.            //调用 doDispatch 方法

  28.            doDispatch(request, response);

  29.        }finally {

  30.            if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {

  31.                // Restore the original attribute snapshot, in case of an include.

  32.                if (attributesSnapshot != null) {

  33.                    restoreAttributesAfterInclude(request, attributesSnapshot);

  34.                }

  35.            }

  36.        }

  37.    }

doDispatch() 方法:

  1. protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {

  2.        HttpServletRequest processedRequest = request;

  3.        HandlerExecutionChain mappedHandler = null;

  4.        boolean multipartRequestParsed =