springmvc执行原理和流程


示例项目见第四部分

1 原理

2 流程

3 源码分析

3.1 启动

对于springmvc,我们会打包成war包部署到tomcat。这里的启动指的是Tomcat的启动。

我们以第四部分示例项目为例说明,在web.xml文件中我们配置了前端控制器DispatcherServlet,并设置了跟随Tomcat一起启动。

我们通过这个博文我们了解到,在Tomcat启动后,容器加载DispatcherServlet时,首先调用init方法。

GenericServlet的init方法

    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
    }

该方法是由Tomcat容器调用的,传入的参数config是容器生成的。

然后执行this.init()调用HttpServletBean的init()方法

    public final void init() throws ServletException {

        // Set bean properties from init parameters.
        PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
        if (!pvs.isEmpty()) {
            try {
                BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
                ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
                bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
                initBeanWrapper(bw);
                bw.setPropertyValues(pvs, true);
            }
            catch (BeansException ex) {
                if (logger.isErrorEnabled()) {
                    logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
                }
                throw ex;
            }
        }

        // Let subclasses do whatever initialization they like.
        initServletBean();
    }

然后调用initServletBean();该方法在FrameworkServlet中实现

    protected final void initServletBean() throws ServletException {
        try {
            this.webApplicationContext = initWebApplicationContext();
            initFrameworkServlet();
        }
        catch (ServletException | RuntimeException ex) {
            logger.error("Context initialization failed", ex);
            throw ex;
        }
    }

我们看一下 this.webApplicationContext = initWebApplicationContext(); 

    protected WebApplicationContext initWebApplicationContext() {
        WebApplicationContext wac = this.webApplicationContext;

        onRefresh(wac);

        return wac;
    }

这里调用了onRefresh()方法,该方法在DispatcherServlet中实现。

    protected void onRefresh(ApplicationContext context) {
        initStrategies(context);
    }

又调用了initStrategies方法

    protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        initHandlerMappings(context);
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }

我们重点看一下 initHandlerMappings(context); 和 initHandlerAdapters(context); ,分别是初始化处理器映射器和处理器适配器。

initHandlerMappings方法初始化处理器映射器,如果web.xml中配置了处理器映射器,将在这里处理。如果web.xml中没有配置处理器映射器,将使用默认的。

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

        // 在ApplicationContext查找所有的HandlerMapping.,如果我们在web.xml中配置了处理器映射器,这里会获取到
        Map matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList<>(matchingBeans.values());
            // We keep HandlerMappings in sorted order.
            AnnotationAwareOrderComparator.sort(this.handlerMappings);
        }

        // 如果web.xml中没有配置处理器映射器,这里添加默认的
        if (this.handlerMappings == null) {
            this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
        }
    }

在该博文末尾提供的实例项目中,我们在web.xml中没有配置处理器映射器,所以会执行

this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);

我们看一下这个方法

    protected  List getDefaultStrategies(ApplicationContext context, Class strategyInterface) {
        String key = strategyInterface.getName();
        String value = defaultStrategies.getProperty(key);
        if (value != null) {
            String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
            List strategies = new ArrayList<>(classNames.length);
            for (String className : classNames) {
                Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
                Object strategy = createDefaultStrategy(context, clazz);
                strategies.add((T) strategy);
            }
            return strategies;
        }
        else {
            return new LinkedList<>();
        }
    }

其中 private static final Properties defaultStrategies; 是一个Properties对象的实例,在该实例中为我们提供了如下2个默认处理器映射器

org.springframework.web.servlet.HandlerMapping -> 
org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping

然后会遍历这两个默认处理器映射器,执行如下方法,我们看一下

Object strategy = createDefaultStrategy(context, clazz);
    protected Object createDefaultStrategy(ApplicationContext context, Class<?> clazz) {
        return context.getAutowireCapableBeanFactory().createBean(clazz);
    }

这里会实例化 BeanNameUrlHandlerMapping 和 RequestMappingHandlerMapping 

我们先看一下实例化 BeanNameUrlHandlerMapping 的过程。

在实例化之前会先执行BeanPostProcessor,其中就包括ApplicationContextAwareProcessor,调用其postProcessBeforeInitialization方法,最终会导致 BeanNameUrlHandlerMapping 其父类的如下方法被调用

    protected void detectHandlers() throws BeansException {
        ApplicationContext applicationContext = obtainApplicationContext();
        String[] beanNames = applicationContext.getBeanNamesForType(Object.class));

        // Take any bean name that we can determine URLs for.
        for (String beanName : beanNames) {
            String[] urls = determineUrlsForHandler(beanName);
            if (!ObjectUtils.isEmpty(urls)) {
                // URL paths found: Let's consider it a handler.
                registerHandler(urls, beanName);
            }
        }
    }

这里获取到applicationContext所有的bean定义,然后遍历这些bean,判断beanname是否以"/"开头,然后添加到列表urls里面返回,这也是为啥web.xml中配置的controller对应的名称必须以"/"开头的原因,如下:

    protected String[] determineUrlsForHandler(String beanName) {
        List urls = new ArrayList<>();
        if (beanName.startsWith("/")) {
            urls.add(beanName);
        }
        String[] aliases = obtainApplicationContext().getAliases(beanName);
        for (String alias : aliases) {
            if (alias.startsWith("/")) {
                urls.add(alias);
            }
        }
        return StringUtils.toStringArray(urls);
    }

如果beanname以"/"开头,则添加到this.handlerMap中

this.handlerMap.put(urlPath, resolvedHandler);

最终,我们得到的这个handlerMapper里面有一个元素

实例化 RequestMappingHandlerMapping  的过程类似,这里不再赘述。

至此处理器映射器初始化完成,处理器映射器里面包含了所有url和controller的对应关系。

我们再回到 DispatcherServlet 的initStrategies方法

    protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        initHandlerMappings(context);
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }

我们接着看处理器适配器的初始化过程 initHandlerAdapters(context); 

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

        // 在ApplicationContext中找到所有的处理器适配器.如果我们在web.xml中配置了处理器适配器,这里会获取到
        Map matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerAdapters = new ArrayList<>(matchingBeans.values());
            // We keep HandlerAdapters in sorted order.
            AnnotationAwareOrderComparator.sort(this.handlerAdapters);
        }

        // 如果web.xml中没有配置处理器适配器,这里创建默认的
        if (this.handlerAdapters == null) {
            this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
        }
    }

在实例项目中,我们没有在web.xml中配置处理器适配器,所以会创建默认的处理器适配器 this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class); 

和处理器映射器的逻辑相同,springmvc为我们准备了3个默认处理器适配器

org.springframework.web.servlet.HandlerAdapter ->
 org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,
 org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,
 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

最终DispatcherServlet中得到一个this.handlerAdapters列表,包含以上3个处理器适配器。

我们再回到DispatcherServlet的initStrategies方法

    protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        initHandlerMappings(context);
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }

同样的分析过程,我们可以分析视图解析器的初始化过程 initViewResolvers(context); ,这里不再赘述,只说明一点,因为我们在实例项目中的web.xml中配置了视图解析器,所以不会创建默认的视图解析器,会使用我们配置的视图解析器。

3.2 处理请求

以示例代码中的get /hello为例

我们在web.xml中配置了DispatcherServlet并且配置了所有url都通过这个Servlet处理。根据Servlet运行原理我们知道,Tomcat容器接收到请求之后,首先调用的是Servlet的service方法。

HttpServlet

    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {

        HttpServletRequest  request;
        HttpServletResponse response;

        try {
            request = (HttpServletRequest) req;
            response = (HttpServletResponse) res;
        } catch (ClassCastException e) {
            throw new ServletException(lStrings.getString("http.non_http"));
        }
        service(request, response);
    }
}

然后,调用重载方法service(request,response)

    protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {

        String method = req.getMethod();

        if (method.equals(METHOD_GET)) {
            doGet(req, resp);
        } else if (method.equals(METHOD_POST)) {
            doPost(req, resp);

        }//其他method省略
    }

根据请求类型,分别调用FrameworkServlet的doGet和doPost方法

    protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        processRequest(request, response);
    }
    protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        processRequest(request, response);
    }

然后,调用processRequest方法(省略了不重要的代码)

    protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doService(request, response);
    }

然后到达DispatcherServlet的doService方法

    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        doDispatch(request, response);
    }

然后调用doDispatch方法,这个方法才是我们要看的重点

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;

        try {
            ModelAndView mv = null;

            // 获取请求对应的处理器(对应的Controller)
            mappedHandler = getHandler(processedRequest);

            // 获取对应的处理器适配器
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
       // 调用拦截器
       mappedHandler.applyPreHandle(processedRequest, response)
       // 调用处理器 
       mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
       // 调用拦截器 mappedHandler.applyPostHandle(processedRequest, response, mv); processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); }
catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } }

首先,获取请求对应的处理器

HandlerExecutionChain mappedHandler = getHandler(processedRequest);

这是一个HandlerExecutionChain对象,里面包含了要调用的Controller和拦截器Interceptor。

然后获取处理器适配器

HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

在3.1我们知道,我们始化了3个处理器适配器,这里遍历这3个处理器适配器,分别调用supports方法,判断是否支持这个handler。

其中SimpleControllerHandlerAdapter的supports方法如下:

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

我们的/hello对应的MyController满足这个条件,因此,返回的处理器适配器就是SimpleControllerHandlerAdapter。

再回到doDispatch方法

我们已经获取到处理器适配器 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); 下一步,调用处理器

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

调用处理器MyController,返回一个ModelAndView对象

public class MyController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //ModelAndView 模型和视图
        ModelAndView mv=new ModelAndView();
        //封装对象,放在ModelAndView中
        mv.addObject("msg", "Hello!SpringMVC!~~");
        //封装要跳转的视图,放在ModelAndView中。
        mv.setViewName("hello"); //WEB-INF/jsp/hello.jsp
        return mv;
    }
}

再回到doDispatch方法,调用processDispatchResult方法

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
            @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
            @Nullable Exception exception) throws Exception {

        render(mv, request, response);
    }

调用render方法

    protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
        String viewName = mv.getViewName();
        View view = resolveViewName(viewName, mv.getModelInternal(), locale, request);

        view.render(mv.getModelInternal(), request, response);
    }

该方法把Controller获取到的ModelAndView解析到Model(也就是要显示的数据),并且把Model塞入View中(jsp中)。

此处有三个点要注意

1 ModelAndView,包含了view名称,model数据

2 将model取出来(要显示的数据),填充view(这里是jsp)

3 对于现在前后端分离的应用,往往这里只是把model(通常是Json字符串)返回。不存在填充view的情况

最终,返回HttpServlet的service方法

    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
    {
        HttpServletRequest  request;
        HttpServletResponse response;

        request = (HttpServletRequest) req;
        response = (HttpServletResponse) res;

        service(request, response);
    }

执行完该方法,res持有了我们要交给Tomcat容器的响应。把响应交给Tomcat容器,Tomcat容器再交给web服务器,web服务器最后交给网卡,网卡将响应封装成报文发送给客户端网卡,客户端网卡接收数据链路发来的物理帧,经过几层协议的转换,最终得到http报文。我们发送请求时一般使用的是浏览器,这里响应肯定也回到浏览器,浏览器解析http报文渲染页面。(当然如果不是浏览器发起的请求,比如curl发起,获取到http报文后没有渲染页面这一步了,直接展示http报文原文),tcp/ip报文传输过程可以参考

4 示例项目

4.1 项目结构

 4.2 web.xml

DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Applicationdisplay-name>
    <context-param>
        <param-name>contextConfigLocationparam-name>
        <param-value>/WEB-INF/applicationContext.xmlparam-value>
    context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
    listener>
    <servlet>
        <servlet-name>dispatcherservlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
        <init-param>
            
            <param-name>contextConfigLocationparam-name>
            <param-value>/WEB-INF/dispatcher-servlet.xmlparam-value>
        init-param>
        <load-on-startup>1load-on-startup>
    servlet>
    <servlet-mapping>
        <servlet-name>dispatcherservlet-name>
        <url-pattern>/url-pattern>
    servlet-mapping>
web-app>

4.3 dispatcher-servlet.xml

XXX-servlet.xml是对XXX这个servlet的配置文件。这里是对web.xml中配置的dispatcher的配置文件。

这里配置了两个bean,分别是视图解析器和我们自定义的MyController

<?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 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">
    

    


    <bean id="defaultViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    bean>
    <bean id="/hello" class="com.szj.controller.MyController" />
beans>

4.4 applicationContext.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">

beans>

4.5 index.jsp

默认首页

<html>
<body>
<h2>Tomcat Server!h2>
body>
html>

4.6 hello.jsp

对应MyController的视图文件。

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Titletitle>
head>
<body>
${msg}
body>
html>

4.7 MyController

自定义处理器,实现了Controller接口,该接口有一个handlerRequest方法。

public class MyController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //ModelAndView 模型和视图
        ModelAndView mv=new ModelAndView();
        //封装对象,放在ModelAndView中
        mv.addObject("msg", "Hello!SpringMVC!~~");
        //封装要跳转的视图,放在ModelAndView中。
        mv.setViewName("hello"); //WEB-INF/jsp/hello.jsp
        return mv;
    }
}