P1M3_MVC框架设计实现及SpringMVC源码分析

133 阅读4分钟

文章内容输出来源:拉钩教育Java高薪训练营# Spring MVC 高级框架

Spring MVC应用

简介

  • 经典三层

    表现层(接受请求,完成响应);业务层(service层,需要保证事务一致性);持久层(dao层,数据库交互);

  • MVC

    表现层的设计模式

    • Model(模型):数据模型封装数据,业务模型处理业务
    • View(视图):展示数据,jsp或者html
    • Controller(控制器):处理用户交互

Spring Web MVC 工作流程

开发过程

  • pom.xml

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.1.12.RELEASE</version>
    </dependency>
    
  • web.xml

    <servlet>
    <!--配置前端控制器DispatcherServlet-->
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!--springmvc的配置文件-->
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    </servlet>
    <servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <!--1. 带后缀 *.action, *.do-->
    <!--2. REST 风格 / 但是会拦截.html等静态资源(除了servlet和jsp之外的js,css,html等等),见url-pattern章节-->
    <!--3. 拦截所有,包括jsp, 不推荐/* -->
    <url-pattern>/</url-pattern>
    </servlet-mapping>
    
  • springmvc.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:mvc="http://www.springframework.org/schema/mvc"
           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/context
           http://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/mvc
           http://www.springframework.org/schema/mvc/spring-mvc.xsd
    ">
    
        <!-- 扫描controller -->
        <context:component-scan base-package="wenbin.carol.wu.controller" />
    
        <!-- 视图解析器 -->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <property name="prefix" value="/WEB-INF/jsp/" />
            <property name="suffix" value=".jsp" />
        </bean>
    
        <!-- 自动适配最合适的处理器、映射器-->
        <mvc:annotation-driven />
    
    </beans>
    
  • Controller.java

    package wenbin.carol.wu.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.servlet.ModelAndView;
    
    import java.util.Date;
    
    @Controller
    @RequestMapping("/demo")
    public class DemoController {
    
        @RequestMapping("/handle01")
        public ModelAndView handle01(){
            Date date = new Date();
            ModelAndView modelAndView = new ModelAndView();
            modelAndView.addObject("date", date);
            // modelAndView.setViewName("/WEB-INF/jsp/success.jsp");
            // 使用视图解析器,逻辑视图名
            modelAndView.setViewName("success");
            return modelAndView;
        }
    }
    
  • success.jsp

    <%--
      Created by IntelliJ IDEA.
      User: Carol
      Date: 2020/10/20
      Time: 1:53 PM
      To change this template use File | Settings | File Templates.
    --%>
    <%@ page isELIgnored="false" pageEncoding="utf-8" contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Title</title>
    </head>
    <body>
    跳转成功,服务器时间:${date}
    </body>
    </html>
    

核心组件

三大件

  • 处理器映射器(HandlerMapping, Map<Url, Handler(处理链Handler+Interceptor)>)
  • 处理器适配器(HandlerAdapter, 根据配置调用handler,返回ModelAndView)
  • 视图解析器(逻辑视图名 -> 物理视图地址)

其余组件

  • HandlerExceptionResolver(异常解析器)
  • RequestToViewNameTranslator (从请求中获得viewname)
  • LocaleResolver(国际化相关)
  • ThemeResolver(主题解析器)
  • MultipartResolver(多部件解析器,文件上传请求)
  • FlashMapManager(重定向的参数传递)

url-pattern配置及原理

如果直接使用/来作为url-pattern,会导致静态资源也被拦截

  • 父 web.xml(tomcat中)配置了default servlet用来处理静态资源(但是被子web.xml覆盖);jsp则因为有JspServlet不受影响

        <servlet>
            <servlet-name>default</servlet-name>
            <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
            <!--省略-->
        </servlet>
    
        <servlet-mapping>
            <servlet-name>default</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    
        <servlet>
            <servlet-name>jsp</servlet-name>
            <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
            <!--省略-->
        </servlet>
    
        <servlet-mapping>
            <servlet-name>jsp</servlet-name>
            <url-pattern>*.jsp</url-pattern>
            <url-pattern>*.jspx</url-pattern>
        </servlet-mapping>
    
  • 处理方式

    1. springmvc.xml添加以下标签,会在springmvc上下文中定义DefaultServletHttpRequestHandler,会检查进入DispatcherServlet的请求,如果是静态资源则返回给tomcat处理。但只能放在webapp文件夹下,存在局限。

      <mvc:default-servlet-handler/>
      
    2. SpringMVC框架自己处理静态资源,存放位置<-->URL规则

      <mvc:resources location="classpath:/" mapping="/resources/**"/>
      <mvc:resources location="/" mapping="/resources/**"/>
      

数据输出机制(Map, Model, ModelMap)

可以直接在handler方法上传入MapModelModelMap参数,并向这些参数中保持数据(放入到请求域)

运行时的具体实现类均为org.springframework.validation.support.BindingAwareModelMap

@RequestMapping("/handle02")
public String handle02(ModelMap modelMap){
    Date date = new Date();
    modelMap.addAttribute("date", date);
    return "success";
}

@RequestMapping("/handle03")
public String handle03(Model model){
    Date date = new Date();
    model.addAttribute("date", date);
    return "success";
}

@RequestMapping("/handle04")
public String handle04(Map<String, Object> map){
    Date date = new Date();
    map.put("date", date);
    return "success";
}

请求参数绑定

  • 示例

    // 原生servlet api支持
    // url:/demo/handle02?id=1
    public String handle02(HttpServletRequest request, HttpServletResponse response, HttpSession session) {
        String id = request.getParameter("id");
        // 省略
    }
    
    // 简单类型参数
    // ⼋种基本数据类型及其包装类型,推荐使用包装类型,因为基础类型不可为null
    // 如果传递的参数名与型参不同,需要使用@RequestParam注解
    // url:/demo/handle03?ids=1&flag=true
    public ModelAndView handle03(@RequestParam("ids") Integer id, Boolean flag) {
    // public ModelAndView handle06(@RequestParam("ids") Integer[] ids, Boolean flag) {
        // 省略
    }
    
    // pojo类型参数
    // 型参名无所谓,但是传递的参数名必须与Pojo属性名保持一致
    // url:/demo/handle04?id=1&username=zhangsan
    public ModelAndView handle04(User user) {
        // 省略
    }
    
    // pojo包装对象(pojo嵌套)
    // 属性名 + "." 的方式进一步锁定
    // url:/demo/handle05?user.id=1&user.username=zhangsan
    public ModelAndView handle05(QueryVo queryVo) {
        // 省略
    }
    
    // 日期类型参数
    // url:/demo/handle06?birthday=2019-10-08
    // 需要自定义一个SpringMVC的接口转换器,扩展实现接口,注册实现
    public ModelAndView handle06(Date birthday) {
        // 省略
    }
    
  • 自定义接口转换器

    DateConverter.java

    package wenbin.carol.wu.converter;
    
    import org.springframework.core.convert.converter.Converter;
    
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    public class DateConverter implements Converter<String, Date> {
        @Override
        public Date convert(String s) {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            try {
                return sdf.parse(s);
            } catch (ParseException e) {
                e.printStackTrace();
            }
            return null;
        }
    }
    

    springmvc.xml

    <!-- 自动适配最合适的处理器、映射器-->
    <mvc:annotation-driven conversion-service="conversionServiceBean"/>
    
    <!--注册自定义类型转换器-->
    <bean id="conversionServiceBean" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="wenbin.carol.wu.converter.DateConverter" />
            </set>
        </property>
    </bean>
    

RESTful风格请求支持

  • url请求的风格,不是标准和协议。可以根据这种风格设计请求的url。

  • 所有东西都是资源,则资源对应唯一的uri标识(统一资源定位符)

  • 根据请求方式不同,代表不同的操作

    • GET 查询,获取资源
    • POST 增加,新建资源
    • PUT 更新
    • DELETE 删除
  • 直观体验:传递参数方式的变化,参数在URI中

  • @PathVariable 示例

    html

    <a href="/demo/handle/15">rest_get</a>
    
    <form method="post" action="/demo/handle">
        <input type="text" name="username" />
        <input type="submit" value="提交rest_post请求" />
    </form>
    
    <form method="post" action="/demo/handle/15/lisi">
        <input type="hidden" name="_method" value="put" />
        <input type="submit" value="提交rest_put请求" />
    </form>
    
    <form method="post" action="/demo/handle/15">
        <input type="hidden" name="_method" value="delete" />
        <input type="submit" value="提交rest_delete请求" />
    </form>
    

    handle

    @RequestMapping(value = "/handle/{id}", method = {RequestMethod.GET})
    public ModelAndView handleGet(@PathVariable("id") Integer id) {
    	// 省略
    }
    
    @RequestMapping(value = "/handle", method = {RequestMethod.POST})
    public ModelAndView handlePost(String username) {
    	// 省略
    }
    
    @RequestMapping(value = "/handle/{id}/{name}", method = {RequestMethod.PUT})
    public ModelAndView handlePut(@PathVariable("id") Integer id, @PathVariable("name") String username) {
    	// 省略
    }
    
    @RequestMapping(value = "/handle/{id}", method = {RequestMethod.DELETE})
    public ModelAndView handleDelete(@PathVariable("id") Integer id) {
    	// 省略
    }
    

    web.xml(添加过滤器,请求方式转换(针对PUT和DELETE))

    <!--配置springmvc请求方式转换过滤器-->
    <!--会检查参数中是否有_method,有则进行转换-->
    <filter>
        <filter-name>hiddenHttpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    </filter>
    
    <filter-mapping>
        <filter-name>hiddenHttpMethodFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    

    web.xml (添加filter过滤器,处理POST请求乱码)

    <!--springmvc提供的针对POST请求的编码过滤器-->
    <filter>
      <filter-name>encoding</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    
    <filter-mapping>
    	<filter-name>encoding</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    

    tomcat下server.xml的配置(处理GET请求乱码)

    <Connector URIEncoding="utf-8" connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443" />
    

Ajax Json交互

  • 前端ajax发送json字符串,后端直接接收为pojo,使用注解 @RequestBody

  • 后端返回pojo对象,前端直接接收为json对象或字符串,使用注解**@ResponseBody**

  • 示例

    pom.xml 引入jar包

    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
      <version>2.9.0</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.9.0</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-annotations</artifactId>
      <version>2.9.0</version>
    </dependency>
    

    handler

    @RequestMapping("/handle20")
    // 添加注解之后,不再⾛视图解析器的流程,⽽是等同于response直接输出数据
    // @ResponseBody
    public @ResponseBody User handle20(@RequestBody User user) {
        user.setName("张三");
        return user;
    }
    

    js

    $(function(){
        $('#ajaxBtn').bind('click', function(){
            // 发送ajax请求
            $.ajax({
                url:'/demo/handle20',
                type:'POST',
                data:'{"id":1,"name":"李四"}',
                contentType:'application/json;charset=utf-8',
                success: function(data){
                    // 拿到json对象
                    alert("success: " + data.name);
                }
            })
        });
    })
    

Spring MVC 高级技术

拦截器(Interceptor)使用

JavaEE标准组件,配置在web.xml中

  • Servlet:处理Request请求和Response响应

  • 过滤器(filter):对Request请求起到过滤作用,在Servlet之前生效(编码过滤器,请求方式转换)

  • 监听器(Listener):随着web应用启动而启动,只初始化一次,然后一直运行监听,随着Web应用停止而销毁

    • 实现org.springframework.web.context.ContextLoaderListener接口

    • 示例一:spring容器启动

        <!--使用监听器启动Spring的IoC容器-->
        <listener>
          <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
      
    • 示例二:监听特定事件,利用HttpSessionLisener统计在线人数等等

SpringMVC,Struts等表现层框架自己的组件,应配置在框架自己的配置文件中

  • 拦截器(Interceptor)

    • 仅拦截访问的控制器方法(Handler),不拦截jsp/html/css/js/image等

    • 实现org.springframework.web.servlet.HandlerInterceptor接口

    • 拦截时机:1. 业务逻辑执行之前(preHandle);2. 业务逻辑执行之后,跳转页面之前(postHandle);3. 跳转页面之后(afterCompletion) image-20201021221017781

    • 示例

      • 单个拦截器

        springmvc.xml

        <mvc:interceptors>
            <!--拦截所有handler-->
            <!--<bean class="wenbin.carol.wu.interceptor.MyInterceptor01" />-->
            <mvc:interceptor>
                <mvc:mapping path="/**"/>
                <!--排除一部分url-->
                <!--<mvc:exclude-mapping path="/demo/**"/>-->
                <bean class="wenbin.carol.wu.interceptor.MyInterceptor01" />
            </mvc:interceptor>
        </mvc:interceptors>
        

        Interceptor.java

        package wenbin.carol.wu.interceptor;
        
        import org.springframework.web.servlet.HandlerInterceptor;
        import org.springframework.web.servlet.ModelAndView;
        
        import javax.servlet.http.HttpServletRequest;
        import javax.servlet.http.HttpServletResponse;
        
        public class MyInterceptor01 implements HandlerInterceptor {
            @Override
            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                System.out.println("MyInterceptor01 preHandle");
                // 是否放行,常用于权限校验
                return true;
            }
        
            @Override
            public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
                System.out.println("MyInterceptor01 postHandle");
            }
        
            @Override
            public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
                                        Exception ex) throws Exception {
                System.out.println("MyInterceptor01 afterCompletion");
            }
        }
        
      • 多个拦截器

        • preHandle:顺序执行 01 -> 02
        • postHandle, afterCompletion:倒序执行 02 -> 01

处理Multipart形式的数据

  • 文件上传,原生servlet是可以处理文件上传的,springmvc又进行了封装

  • 要点

    • 客户端
      • form表单
      • method=POST
      • enctype=multipart
      • file组件
    • 服务端
      • 重命名(唯一标识)
      • 存储到磁盘(同一个目录不能文件过多,例如按照日期创建新的文件夹)
      • 存储路径更新到数据库
    • 文件上传解析器
  • 示例

    pom.xml引入jar包

    <dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.3.1</version>
    </dependency>
    

    前端

    <fieldset>
        <form method="post" enctype="multipart/form-data" action="/demo/upload">
            <input type="file" name="uploadFile">
            <input type="submit" value="上传">
        </form>
    </fieldset>
    

    后端

    @RequestMapping("/upload")
    public ModelAndView upload(MultipartFile uploadFile, HttpSession session) throws IOException {
    
        String originalFilename = uploadFile.getOriginalFilename();
        if (originalFilename != null) {
            String ext = originalFilename.substring(originalFilename.lastIndexOf('.') + 1);
            String newFilename = UUID.randomUUID().toString() + "." + ext;
    
            String realPath = session.getServletContext().getRealPath("/uploads");
            String datePath = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
    
            File folder = new File(realPath + "/" + datePath);
            if (!folder.exists()) {
                // mkdirs会创建路径上需要的所有文件夹
                folder.mkdirs();
            }
    
            uploadFile.transferTo(new File(folder, newFilename));
    
        }
        Date date = new Date();
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("date",date);
        modelAndView.setViewName("success");
        return modelAndView;
    }
    

    springmvc配置,id固定为multipartResolver

    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!--文件大小上限,单位字节,默认-1无限制-->
        <property name="maxUploadSize" value="5000000" />
    </bean>
    

异常处理

  1. 实现org.springframework.web.servlet.HandlerExceptionResolver 接口

  2. 使用注解@ExceptionHandler

    • 示例1

      @Controller
      @RequestMapping("/demo")
      public class DemoController {
      
          // SpringMVC的异常处理机制(异常处理器)
          // 只对当前的类生效
          @ExceptionHandler(ArithmeticException.class)
          public void handleException(ArithmeticException e, HttpServletResponse response) {
              try {
                  response.getWriter().write(e.getMessage());
              } catch (IOException ex) {
                  ex.printStackTrace();
              }
          }
      
          @RequestMapping("/handle01")
          public ModelAndView handle01(){
              // 省略
          }
      }
      
    • 示例2

      // 捕获全局所有controller对象抛出的异常
      @ControllerAdvice
      public class GlobalExceptionResolver {
      
          @ExceptionHandler(ArithmeticException.class)
          public ModelAndView handleException(ArithmeticException e, HttpServletResponse response) {
              ModelAndView modelAndView = new ModelAndView();
              modelAndView.addObject("exception", e.getMessage());
              modelAndView.setViewName("fail");
              return modelAndView;
          }
      }
      

重定向数据传递

  • 转发 v.s. 重定向

    • 转发:url不会变,参数也不会丢失
    • 重定向:url会变,参数也丢失
  • 示例

    @RequestMapping("/handleRedirect")
    public String handleRedirect(String name, RedirectAttributes redirectAttributes) {
        // 方法一:拼接,get请求参数长度有限制,且不安全
        // return "redirect:handle30?name=" + name;
        // 或者(等价)
        // redirectAttributes.addAttribute("name", name);
        // return "redirect:handle30";
        // 方法二:flash属性机制,向session中记录,跳转之后销毁
        redirectAttributes.addFlashAttribute("name", name);
        return "redirect:handle30";
    }
    

手写MVC框架

  • 原理步骤

    1. tomcat加载web.xml

      配置前端控制器DispatcherServlet

      加载配置文件springmvc.xml

    2. 包扫描,扫描注解

      @Controller

      @Service

      @RequestMapping @AutoWired

    3. Bean初始化及依赖注入

    4. SpringMVC相关组件的初始化

      建立url和method之间的映射关系(HandlerMapping)

    5. 等待请求进入,处理请求

  • 项目代码

    gitee.com/carol7/repo…

Spring MVC 源码深度剖析

DispatcherServlet 前端控制器

  • 继承结构:DispatcherServlet <--- FrameworkServlet <--- HttpServletBean <--- HttpServlet

  • 请求时序

image-20201024150847372

doDispatch 重要时机分析

	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 {
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// Determine handler for the current request.
                // 1. 获得处理当前请求的执行链 HandlerExecutionChain
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
                // 2. 获取能够执行handler的适配器HandlerAdapter
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				String method = request.getMethod();
				boolean isGet = "GET".equals(method);
				if (isGet || "HEAD".equals(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}

                // 拦截器 preHandle
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// Actually invoke the handler.
                // 3. 适配器调用Handler执行,返回ModelAndView对象
				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;
			}
			catch (Throwable err) {
				// As of 4.3, we're processing Errors thrown from handler methods as well,
				// making them available for @ExceptionHandler methods and other scenarios.
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}
            // 4. 视图渲染跳转
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("Handler processing failed", err));
		}
		finally {
			if (asyncManager.isConcurrentHandlingStarted()) {
				// Instead of postHandle and afterCompletion
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				// Clean up any resources used by a multipart request.
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}

getHandler&getHandlerAdaptor

image-20201024161306863

image-20201024161406496

组件初始化

org.springframework.context.support.AbstractApplicationContext#refresh
|
org.springframework.context.support.AbstractApplicationContext#finishRefresh
|
发布事件&监听
|
org.springframework.web.servlet.DispatcherServlet#onRefresh
|
org.springframework.web.servlet.DispatcherServlet#initStrategies
|
org.springframework.web.servlet.DispatcherServlet#initHandlerMappings(以及其他组件)

initHandlerMappings为例

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

    // 1. 可配参数,默认为true
	if (this.detectAllHandlerMappings) {
        // 寻找所有实现了HandlerMapping的bean
		// 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<>(matchingBeans.values());
			// We keep HandlerMappings in sorted order.
			AnnotationAwareOrderComparator.sort(this.handlerMappings);
		}
	}
	else {
		try {
            // 2. 按照固定id, "handerMapping",加载自定义bean
			HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
			this.handlerMappings = Collections.singletonList(hm);
		}
		catch (NoSuchBeanDefinitionException ex) {
			// Ignore, we'll add a default HandlerMapping later.
		}
	}

    // 3. 均没有则使用default,---> DispatcherServlet.properties
	// 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.isTraceEnabled()) {
			logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
					"': using default strategies from DispatcherServlet.properties");
		}
	}
}

只有initMultipartResolver不同,必须使用固定的id

private void initMultipartResolver(ApplicationContext context) {
	try {
        // public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";
		this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
        // 省略
	}
	catch (NoSuchBeanDefinitionException ex) {
		// 省略
	}
}

handler方法执行细节

org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#handle
|
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal
|
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod
|
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle
|
org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest
|
org.springframework.web.method.support.InvocableHandlerMethod#doInvoke

参数解析

image-20201024170841305

视图渲染细节剖析

  • 获取view
org.springframework.web.servlet.DispatcherServlet#processDispatchResult
|
org.springframework.web.servlet.DispatcherServlet#render
|
org.springframework.web.servlet.DispatcherServlet#resolveViewName
|
org.springframework.web.servlet.view.AbstractCachingViewResolver#resolveViewName
|
org.springframework.web.servlet.view.UrlBasedViewResolver#createView
|
org.springframework.web.servlet.view.UrlBasedViewResolver#buildView

image-20201024171932911

redirect:RedirectView
forward:InternalResourceView

image-20201024172453169

  • 渲染
org.springframework.web.servlet.DispatcherServlet#processDispatchResult
|
org.springframework.web.servlet.DispatcherServlet#render
|
org.springframework.web.servlet.view.AbstractView#render
|
org.springframework.web.servlet.view.InternalResourceView#renderMergedOutputModel(见下图)
|
org.apache.catalina.core.ApplicationDispatcher#forward

把modelmap中的数据暴露到request域中,model.add() --> jsp可以取出的原因

image-20201024175249188