SpringMVC应用及源码分析

一、SpringMVC应用

1、SpringMVC简介

1.1、MVC 体系结构

三层架构
我们的开发架构⼀般都是基于两种形式,⼀种是 C/S 架构,也就是客户端/服务器;另⼀种是 B/S 架构,也就是浏览器服务器。在 JavaEE 开发中,几乎全都是基于 B/S 架构的开发。那么在 B/S 架构中,系统标准的三层架构包括:表现层、业务层、持久层。三层架构在我们的实际开发中使⽤的非常多。
三层架构中,每⼀层各司其职,接下来我们就说说每层都负责哪些方面:

  • 表现层 :
    也就是我们常说的web 层。它负责接收客户端请求,向客户端响应结果,通常客户端使用http 协议请求web 层,web 需要接收 http 请求,完成 http 响应。

表现层包括展示层和控制层:控制层负责接收请求,展示层负责结果的展示。

表现层依赖业务层,接收到客户端请求⼀般会调⽤业务层进⾏业务处理,并将处理结果响应给客户端。

表现层的设计⼀般都使⽤ MVC 模型。(MVC 是表现层的设计模型,和其他层没有关系)

  • 业务层 :
    也就是我们常说的 service 层。它负责业务逻辑处理,和我们开发项⽬的需求息息相关。web 层依赖业务层,但是业务层不依赖 web 层。

业务层在业务处理时可能会依赖持久层,如果要对数据持久化需要保证事务⼀致性。(也就是我们说
的, 事务应该放到业务层来控制)

  • 持久层 :
    也就是我们是常说的 dao 层。负责数据持久化,包括数据层即数据库和数据访问层,数据库是对数据进行持久化的载体,数据访问层是业务层和持久层交互的接口,业务层需要通过数据访问层将数据持久化到数据库中。通俗的讲,持久层就是和数据库交互,对数据库表进行增删改查的。

MVC设计模式
MVC 全名是 Model View Controller,是 模型(model)-视图(view)-控制器(controller) 的缩写, 是⼀
种⽤于设计创建 Web 应⽤程序表现层的模式。MVC 中每个部分各司其职:

  • Model(模型):模型包含业务模型和数据模型,数据模型⽤于封装数据,业务模型⽤于处理业务。
  • View(视图): 通常指的就是我们的 jsp 或者 html。作⽤⼀般就是展示数据的。通常视图是依据模型数据创建的。
  • Controller(控制器): 是应⽤程序中处理⽤户交互的部分。作用⼀般就是处理程序逻辑的。

MVC提倡:每⼀层只编写自己的东西,不编写任何其他的代码;分层是为了解耦,解耦是为了维护方便和分工协作。

1.2、SpringMVC是什么?

SpringMVC 全名叫 Spring Web MVC,是⼀种基于 Java 的实现 MVC 设计模型的请求驱动类型的轻量级Web 框架,属于 SpringFrameWork 的后续产品。
在这里插入图片描述
SpringMVC 已经成为目前最主流的 MVC 框架之⼀,并且随着 Spring3.0 的发布,全面超越 Struts2,成为最优秀的 MVC 框架。

servlet、struts实现接⼝、springmvc中要让⼀个java类能够处理请求只需要添加注解就ok

它通过⼀套注解,让⼀个简单的 Java 类成为处理请求的控制器,而无须实现任何接口。同时它还⽀持RESTful 编程风格的请求。

总之:Spring MVC和Struts2⼀样,都是 为了解决表现层问题 的web框架,它们都是基于 MVC 设计模式的。⽽这些表现层框架的主要职责就是处理前端HTTP请求。

Spring MVC 本质可以认为是对servlet的封装,简化了我们serlvet的开发
作用:1)接收请求 2)返回响应,跳转页面
在这里插入图片描述

2、Spring Web MVC 工作流程

2.1、 Spring MVC 请求处理流程

在这里插入图片描述
流程说明

第⼀步:用户发送请求⾄前端控制器DispatcherServlet

第二步:DispatcherServlet收到请求调⽤HandlerMapping处理器映射器

第三步:处理器映射器根据请求Url找到具体的Handler(后端控制器),生成处理器对象及处理器拦截器(如果有则生成)⼀并返回DispatcherServlet

第四步:DispatcherServlet调用HandlerAdapter处理器适配器去调用Handler

第五步:处理器适配器执行Handler

第六步:Handler执行完成给处理器适配器返回ModelAndView

第七步:处理器适配器向前端控制器返回 ModelAndView,ModelAndView 是SpringMVC 框架的⼀个底层对象,包括 Model 和 View

第⼋步:前端控制器请求视图解析器去进⾏视图解析,根据逻辑视图名来解析真正的视图。

第九步:视图解析器向前端控制器返回View

第十步:前端控制器进行视图渲染,就是将模型数据(在 ModelAndView 对象中)填充到 request 域

第十一步:前端控制器向用户响应结果

2.2、Spring MVC 九大组件

  • HandlerMapping(处理器映射器)

HandlerMapping 是用来查找 Handler 的,也就是处理器,具体的表现形式可以是类,也可以是方法。比如,标注了@RequestMapping的每个⽅法都可以看成是⼀个Handler。Handler负责具体实际的请求处理,在请求到达后,HandlerMapping 的作用便是找到请求相应的处理器
Handler 和 Interceptor.

  • HandlerAdapter(处理器适配器)

HandlerAdapter 是⼀个适配器。因为 Spring MVC 中 Handler 可以是任意形式的,只要能处理请求即可。但是把请求交给 Servlet 的时候,由于 Servlet 的方法结构都是
doService(HttpServletRequest req,HttpServletResponse resp)形式的,要让固定的 Servlet 处理方法调用 Handler 来进⾏处理,便是 HandlerAdapter 的职责。

  • HandlerExceptionResolver

HandlerExceptionResolver ⽤于处理 Handler 产生的异常情况。它的作用是根据异常设置ModelAndView,之后交给渲染方法进行渲染,渲染方法会将 ModelAndView 渲染成页面。

  • ViewResolver

ViewResolver即视图解析器,⽤于将String类型的视图名和Locale解析为View类型的视图,只有⼀
个resolveViewName()⽅法。从⽅法的定义可以看出,Controller层返回的String类型视图名
viewName 最终会在这里被解析成为View。View用来渲染页面的,也就是说,它会将程序返回
的参数和数据填⼊模板中,生成html⽂件。ViewResolver 在这个过程主要完成两件事情:
ViewResolver 找到渲染所用的模板(第⼀件⼤事)和所⽤的技术(第⼆件⼤事,其实也就是找到
视图的类型,如JSP)并填⼊参数。默认情况下,Spring MVC会自动为我们配置⼀个InternalResourceViewResolver,是针对 JSP 类型视图的。

  • RequestToViewNameTranslator

RequestToViewNameTranslator 组件的作⽤是从请求中获取 ViewName.因为 ViewResolver 根据
ViewName 查找 View,但有的 Handler 处理完成之后,没有设置 View,也没有设置 ViewName,
便要通过这个组件从请求中查找 ViewName。

  • LocaleResolver

ViewResolver 组件的 resolveViewName ⽅法需要两个参数,⼀个是视图名,⼀个是 Locale。
LocaleResolver ⽤于从请求中解析出 Locale,⽐如中国 Locale 是 zh-CN,⽤来表示⼀个区域。这
个组件也是 i18n 的基础。

  • ThemeResolver

ThemeResolver 组件是用来解析主题的。主题是样式、图片及它们所形成的显示效果的集合。
Spring MVC 中⼀套主题对应⼀个 properties⽂件,里面存放着与当前主题相关的所有资源,如图片、CSS样式等。创建主题非常简单,只需准备好资源,然后新建⼀个“主题名.properties”并将资源设置进去,放在classpath下,之后便可以在页面中使⽤了。SpringMVC中与主题相关的类有
ThemeResolver、ThemeSource和Theme。ThemeResolver负责从请求中解析出主题名,
ThemeSource根据主题名找到具体的主题,其抽象也就是Theme,可以通过Theme来获取主题和
具体的资源。

  • MultipartResolver

MultipartResolver 用于上传请求,通过将普通的请求包装成 MultipartHttpServletRequest 来实
现。MultipartHttpServletRequest 可以通过 getFile() 方法 直接获得文件。如果上传多个文件,还
可以调用 getFileMap()⽅法得到Map<FileName,File>这样的结构,MultipartResolver 的作⽤就
是封装普通的请求,使其拥有文件上传的功能。

  • FlashMapManager

FlashMap 用于重定向时的参数传递,比如在处理用户订单时候,为了避免重复提交,可以处理完post请求之后重定向到⼀个get请求,这个get请求可以用来显示订单详情之类的信息。这样做虽然可以规避用户重新提交订单的问题,但是在这个页面上要显示订单的信息,这些数据从哪⾥来获得
呢?因为重定向时么有传递参数这⼀功能的,如果不想把参数写进URL(不推荐),那么就可以通
过FlashMap来传递。只需要在重定向之前将要传递的数据写⼊请求(可以通过
ServletRequestAttributes.getRequest()方法获得)的属性OUTPUT_FLASH_MAP_ATTRIBUTE
中,这样在重定向之后的Handler中Spring就会自动将其设置到Model中,在显示订单信息的页面
上就可以直接从Model中获取数据。FlashMapManager 就是⽤来管理 FalshMap 的。

3、请求参数绑定

请求参数绑定:说白了SpringMVC如何接收请求参数
http协议(超文本传输协议)
原生servlet接收⼀个整型参数:
1)String ageStr = request.getParameter(“age”);
2)Integer age = Integer.parseInt(ageStr);
SpringMVC框架对Servlet的封装,简化了servlet的很多操作
SpringMVC在接收整型参数的时候,直接在Handler⽅法中声明形参即可
@RequestMapping(“xxx”)
public String handle(Integer age) {
System.out.println(age);
}
参数绑定:取出参数值绑定到handler⽅法的形参上

  • 默认⽀持 Servlet API 作为方法参数
    当需要使用HttpServletRequest、HttpServletResponse、HttpSession等原⽣servlet对象时,直接在handler方法中形参声明使⽤即可。
/**
 *
 * SpringMVC 对原⽣servlet api的⽀持 url:/demo/handle02?id=1
 *
 * 如果要在SpringMVC中使⽤servlet原⽣对象,⽐如
HttpServletRequest\HttpServletResponse\HttpSession,直接在Handler⽅法形参中声
明使⽤即可
 *
 */
@RequestMapping("/handle02")
public ModelAndView handle02(HttpServletRequest request, HttpServletResponse response,HttpSession session) {
 	String id = request.getParameter("id");
 	Date date = new Date();
 	ModelAndView modelAndView = new ModelAndView();
 	modelAndView.addObject("date",date);
 	modelAndView.setViewName("success");
 	return modelAndView;
}
  • 绑定简单类型参数
    简单数据类型:八种基本数据类型及其包装类型
    参数类型推荐使⽤包装数据类型,因为基础数据类型不可以为null
    整型:Integer、int
    字符串:String
    单精度:Float、float
    双精度:Double、double
    布尔型:Boolean、boolean
    说明:对于布尔类型的参数,请求的参数值为true或false。或者1或0
    注意:绑定简单数据类型参数,只需要直接声明形参即可(形参参数名和传递的参数名要保持⼀致,建议 使用包装类型,当形参参数名和传递参数名不⼀致时可以使用@RequestParam注解进行手动映射)
/*
 * SpringMVC 接收简单数据类型参数 url:/demo/handle03?id=1
 *
 * 注意:接收简单数据类型参数,直接在handler⽅法的形参中声明即可,框架会取出参数值然后绑定到对应参数上
 * 要求:传递的参数名和声明的形参名称保持⼀致
 */
 @RequestMapping("/handle03")
 public ModelAndView handle03(@RequestParam("ids") Integer id,Boolean flag) {
 	Date date = new Date();
 	ModelAndView modelAndView = new ModelAndView();
	modelAndView.addObject("date",date);
	modelAndView.setViewName("success");
	return modelAndView;
 }
  • 绑定Pojo类型参数
/*
 * SpringMVC接收pojo类型参数 url:/demo/handle04?id=1&username=zhangsan
 *
 * 接收pojo类型参数,直接形参声明即可,类型就是Pojo的类型,形参名⽆所谓
 * 但是要求传递的参数名必须和Pojo的属性名保持⼀致
 */
 @RequestMapping("/handle04")
 public ModelAndView handle04(User user) {
 	Date date = new Date();
	ModelAndView modelAndView = new ModelAndView();
 	modelAndView.addObject("date",date);
 	modelAndView.setViewName("success");
 	return modelAndView;
 }
  • 绑定Pojo包装对象参数
    包装类型 QueryVo
public class QueryVo {
 private String mail;
 private String phone;
 // 嵌套了另外的Pojo对象
 private User user;
 public String getMail() {
 	return mail;
 }
 public void setMail(String mail) {
 	this.mail = mail;
 }
 public String getPhone() {
 	return phone;
 }
 public void setPhone(String phone) {
 	this.phone = phone;
 }
 public User getUser() {
 	return user;
 }
 public void setUser(User user) {
 	this.user = user;
 }
}

Handler方法

/*
 * SpringMVC接收pojo包装类型参数 url:/demo/handle05?
user.id=1&user.username=zhangsan
 * 不管包装Pojo与否,它⾸先是⼀个pojo,那么就可以按照上述pojo的要求来
 * 1、绑定时候直接形参声明即可
 * 2、传参参数名和pojo属性保持⼀致,如果不能够定位数据项,那么通过属性名 + "." 的⽅式进⼀步锁定数据
 *
 */
 @RequestMapping("/handle05")
 public ModelAndView handle05(QueryVo queryVo) {
 	Date date = new Date();
 	ModelAndView modelAndView = new ModelAndView();
 	modelAndView.addObject("date",date);
 	modelAndView.setViewName("success");
 	return modelAndView;
 }
  • 绑定⽇期类型参数(需要配置自定义类型转换器)
    • 前端jsp
    <fieldset>
    	<p>测试⽤例:SpringMVC接收日期类型参数</p>
    	<a href="/demo/handle06?birthday=2019-10-08">点击测试</a>
    </fieldset>
    
    • 后台Handler方法
    /**
    * 绑定⽇期类型参数
     * 定义⼀个SpringMVC的类型转换器 接口,扩展实现接口接口,注册你的实现
     * @param birthday
     * @return
     */
     @RequestMapping("/handle06")
     public ModelAndView handle06(Date birthday) {
     	Date date = new Date();
     	ModelAndView modelAndView = new ModelAndView();
     	modelAndView.addObject("date",date);
     	modelAndView.setViewName("success");
     	return modelAndView;
     }
    
    • 自定义类型转换器
    public class DateConverter implements Converter<String, Date> {
     @Override
     public Date convert(String source) {
     	// 完成字符串向⽇期的转换
     	SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
     	try {
    		 Date parse = simpleDateFormat.parse(source);
    		 return parse;
    	 } catch (ParseException e) {
    		 e.printStackTrace();
    	 }
    		 return null;
    	 }
     }
    
    • 注册自定义类型转换器
    <!--⾃动注册最合适的处理器映射器,处理器适配器(调⽤handler⽅法) -->
    <mvc:annotation-driven conversionservice="conversionServiceBean"/>
    <!--注册⾃定义类型转换器-->
    <bean id="conversionServiceBean" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    	<property name="converters">
    		<set>
    			<bean class="com.lagou.edu.converter.DateConverter"></bean>
    		</set>
    	</property>
    </bean>
    
    

4、对Restful风格请求支持

get 查询,获取资源
post 增加,新建资源
put 更新
delete 删除资源
rest风格带来的直观体现:就是传递参数方式的变化,参数可以在uri中了

springmvc对rest风格请求到底提供了怎样的⽀持
是⼀个注解的使用@PathVariable,可以帮助我们从uri中取出参数

将特定的post请求转换为put和delete请求配置springmvc请求方式转换过滤器,会检查请求参数中是否有_method参数,如果有就按照指定的请求方式进行转换:

org.springframework.web.filter.HiddenHttpMethodFilter

5、Ajax Json交互

交互:两个方向

1)前端到后台:前端ajax发送json格式字符串,后台直接接收为pojo参数,使⽤注解@RequstBody

2)后台到前端:后台直接返回pojo对象,前端直接接收为json对象或者字符串,使⽤注解
@ResponseBody

二、SpringMVC高级技术

1、拦截器(Inteceptor)使用

1.1、监听器、过滤器和拦截器对比

  • Servlet:处理Request请求和Response响应
  • 过滤器(Filter):对Request请求起到过滤的作⽤,作⽤在Servlet之前,如果配置为/*可以对所有的资源访问(servlet、js/css静态资源等)进行过滤处理
  • 监听器(Listener):实现了javax.servlet.ServletContextListener 接⼝的服务器端组件,它随Web应⽤的启动而启动,只初始化⼀次,然后会⼀直运行监视,随Web应用的停止而销毁
    作用一:做⼀些初始化工作,web应⽤中spring容器启动ContextLoaderListener
    作用二:监听web中的特定事件,比如HttpSession,ServletRequest的创建和销毁;变量的创建、销毁和修改等。可以在某些动作前后增加处理,实现监控,比如统计在线人数,利用
    HttpSessionLisener等。
  • 拦截器(Interceptor):是SpringMVC、Struts等表现层框架自己的,不会拦截jsp/html/css/image的访问等,只会拦截访问的控制器方法(Handler)。
    从配置的角度也能够总结发现:serlvet、filter、listener是配置在web.xml中的,而interceptor是
    配置在表现层框架自己的配置文件中的
    • 在Handler业务逻辑执行之前拦截⼀次
    • 在Handler逻辑执行完毕但未跳转页面之前拦截⼀次
    • 在跳转页面之后拦截⼀次

1.2、拦截器的执行流程

在运行程序时,拦截器的执行是有⼀定顺序的,该顺序与配置文件中所定义的拦截器的顺序相关。 单个拦截器,在程序中的执行流程如下图所示:
在这里插入图片描述
1)程序先执⾏preHandle()⽅法,如果该⽅法的返回值为true,则程序会继续向下执⾏处理器中的⽅法,否则将不再向下执⾏。

2)在业务处理器(即控制器Controller类)处理完请求后,会执⾏postHandle()⽅法,然后会通过DispatcherServlet向客户端返回响应。

3)在DispatcherServlet处理完请求后,才会执⾏afterCompletion()⽅法。

1.3多个拦截器的执行流程

多个拦截器(假设有两个拦截器Interceptor1和Interceptor2,并且在配置⽂件中, Interceptor1拦截器配置在前),在程序中的执⾏流程如下图所示:

在这里插入图片描述
从图可以看出,当有多个拦截器同时工作时,它们的preHandle()方法会按照配置文件中拦截器的配置顺序执行,而它们的postHandle()方法和afterCompletion()方法则会按照配置顺序的反序执行。

2、处理multipart形式的数据

文件上传
原生servlet处理上传的文件数据的,springmvc又是对serlvet的封装
所需jar包

<!--⽂件上传所需jar坐标-->
<dependency>
 <groupId>commons-fileupload</groupId>
 <artifactId>commons-fileupload</artifactId>
 <version>1.3.1</version>
</dependency>

配置⽂件上传解析器

<!--配置⽂件上传解析器,id是固定的multipartResolver-->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
 <!--设置上传⼤⼩,单位字节-->
 <property name="maxUploadSize" value="1000000000"/>
</bean>

前端Form

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

后台接收Handler

@RequestMapping("upload")
public String upload(MultipartFile uploadFile, HttpServletRequest request) throws IOException {
	 // ⽂件原名,如xxx.jpg
	 String originalFilename = uploadFile.getOriginalFilename();
	 // 获取⽂件的扩展名,如jpg
	 String extendName = originalFilename.substring(originalFilename.lastIndexOf(".") + 1, originalFilename.length());
	 String uuid = UUID.randomUUID().toString();
	 // 新的⽂件名字
	 String newName = uuid + "." + extendName;
	 String realPath = request.getSession().getServletContext().getRealPath("/uploads");
	 // 解决⽂件夹存放⽂件数量限制,按⽇期存放
	 String datePath = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
	 File floder = new File(realPath + "/" + datePath);
	 if(!floder.exists()) {
	 floder.mkdirs();
	 }
	 uploadFile.transferTo(new File(floder,newName));
	 return "success";
}

3、在控制器中处理异常

// 可以让我们优雅的捕获所有Controller对象handler⽅法抛出的异常
@ControllerAdvice
public class GlobalExceptionResolver {
	 @ExceptionHandler(ArithmeticException.class)
	 public ModelAndView handleException(ArithmeticException exception, HttpServletResponse response) {
		 ModelAndView modelAndView = new ModelAndView();
		 modelAndView.addObject("msg",exception.getMessage());
		 modelAndView.setViewName("error");
		 return modelAndView;
	 }
}

4、基于Flash属性的跨重定向请求数据传递

重定向时请求参数会丢失,我们往往需要重新携带请求参数,我们可以进⾏⼿动参数拼接如下:

return "redirect:handle01?name=" + name;

但是上述拼接参数的方法属于get请求,携带参数长度有限制,参数安全性也不高,此时,我们可以使用SpringMVC提供的flash属性机制,向上下文中添加flash属性,框架会在session中记录该属性值,当跳转到页面之后框架会自动删除flash属性,不需要我们手动删除,通过这种方式进行重定向参数传递,参数长度和安全性都得到了保障,如下:

/**
 * SpringMVC 重定向时参数传递的问题
 * 转发:A 找 B 借钱400,B没有钱但是悄悄的找到C借了400块钱给A
 * url不会变,参数也不会丢失,⼀个请求
 * 重定向:A 找 B 借钱400,B 说我没有钱,你找别⼈借去,那么A ⼜带着400块的借钱需求找到C
 * url会变,参数会丢失需要重新携带参数,两个请求
 */
 @RequestMapping("/handleRedirect")
 public String handleRedirect(String name,RedirectAttributes redirectAttributes) {
	 // return "redirect:handle01?name=" + name; // 拼接参数安全性、参数⻓度都有局限
	 // addFlashAttribute⽅法设置了⼀个flash类型属性,该属性会被暂存到session中,在跳转到⻚⾯之后该属性销毁
	 redirectAttributes.addFlashAttribute("name",name);
	 return "redirect:handle01";
 }

三、手写MVC框架

回顾SpringMVC执行的大致原理,后续根据这个模仿手写自己的mvc框架就可以了
在这里插入图片描述

四、SpringMVC源码剖析

1、前端控制器 DispatcherServlet 继承结构

在这里插入图片描述

2、重要时机点分析

1)Handler⽅法的执⾏时机

打断点
在这里插入图片描述
观察调用栈
在这里插入图片描述
doDispatch方法中的1064行代码完成handler方法的调用
也就是mv = ha.handle(…)

2)页面渲染时机(打断点并观察调用栈)

在这里插入图片描述

  • SpringMVC处理请求的流程即为
    org.springframework.web.servlet.DispatcherServlet#doDispatch方法的执行过程,其中步骤
    2、3、4、5是核心步骤
    1)调用getHandler()获取到能够处理当前请求的执行链 HandlerExecutionChain(Handler+拦截器)但是如何去getHandler的?后面进行分析
    2)调用getHandlerAdapter();获取能够执行1)中Handler的适配器,但是如何去getHandlerAdapter的?后面进行分析
    3)适配器调用Handler执行ha.handle(总会返回⼀个ModelAndView对象)
    4)调⽤processDispatchResult()方法完成视图渲染跳转
	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 {
				// 1 检查是否是文件上传的请求
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// Determine handler for the current request.
				/**
				 * 2 取得处理当前请求的Controller,这里也称为Handler,即处理器
				 *   这里并不是直接返回Controller,而是返回 HandlerExecutionChain 请求处理链对象
				 *   该对象封装了Handler和Inteceptor
				 */
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					// 如果 handler 为空,则返回404
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
				// 3 获取处理请求的处理器适配器 HandlerAdapter
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the 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 (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}
				// 拦截器的第一个拦截时机
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// Actually invoke the handler.
				// 4 实际处理器处理请求,返回结果视图对象
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}
				// 结果视图对象的处理
				applyDefaultViewName(processedRequest, mv);
				// 拦截器的第二个拦截时机
				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);
			}
			// 跳转视图界面,渲染视图
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			// 最终会调用 HandlerInterceptor 的 afterCompletion 方法
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			// 最终会调用 HandlerInterceptor 的 afterCompletion 方法
			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);
				}
			}
		}
	}

3、核心步骤getHandler方法剖析

遍历两个HandlerMapping,试图获取能够处理当前请求的执行链
在这里插入图片描述

4、核心步骤getHandlerAdapter方法剖析

遍历各个HandlerAdapter,看哪个Adapter支持处理当前Handler
在这里插入图片描述

5、核心步骤ha.handle方法剖析

  • ⼊⼝
    在这里插入图片描述
  • 断点从入口进入
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

6、核心步骤processDispatchResult方法剖析

  • render方法完成渲染
    在这里插入图片描述
  • 视图解析器解析出View视图对象
    在这里插入图片描述
  • 在解析出View视图对象的过程中会判断是否重定向、是否转发等,不同的情况封装的是不同的View实现
    在这里插入图片描述
  • 解析出View视图对象的过程中,要将逻辑视图名解析为物理视图名
    在这里插入图片描述
  • 封装View视图对象之后,调⽤了view对象的render⽅法
    在这里插入图片描述
  • 渲染数据
    在这里插入图片描述
  • 把modelMap中的数据暴露到request域中,这也是为什么后台model.add之后在jsp中可以从请求域取出来的根本原因
    在这里插入图片描述
  • 将数据设置到请求域中
    在这里插入图片描述

7、SpringMVC九大组件初始化

1)在DispatcherServlet中定义了九个属性,每⼀个属性都对应⼀种组件

/** MultipartResolver used by this servlet. */
// 多部件解析器
@Nullable
private MultipartResolver multipartResolver;

/** LocaleResolver used by this servlet. */
// 区域化 国际化解析器
@Nullable
private LocaleResolver localeResolver;

/** ThemeResolver used by this servlet. */
// 主题解析器
@Nullable
private ThemeResolver themeResolver;

/** List of HandlerMappings used by this servlet. */
// 处理器映射器组件
@Nullable
private List<HandlerMapping> handlerMappings;

/** List of HandlerAdapters used by this servlet. */
// 处理器适配器组件
@Nullable
private List<HandlerAdapter> handlerAdapters;

/** List of HandlerExceptionResolvers used by this servlet. */
// 异常解析器组件
@Nullable
private List<HandlerExceptionResolver> handlerExceptionResolvers;

/** RequestToViewNameTranslator used by this servlet. */
// 默认视图名转换器组件
@Nullable
private RequestToViewNameTranslator viewNameTranslator;

/** FlashMapManager used by this servlet. */
// flash属性管理组件
@Nullable
private FlashMapManager flashMapManager;

/** List of ViewResolvers used by this servlet. */
// 视图解析器
@Nullable
private List<ViewResolver> viewResolvers;

九⼤组件都是定义了接⼝,接⼝其实就是定义了该组件的规范,⽐如ViewResolver、HandlerAdapter等都是接⼝
2)九⼤组件的初始化时机

  • DispatcherServlet中的onRefresh(),该⽅法中初始化了九⼤组件
    在这里插入图片描述
  • initStrategies⽅法
    在这里插入图片描述
  • 观察其中的⼀个组件initHandlerMappings(context)
    在这里插入图片描述在这里插入图片描述
  • 如果按照类型和按照固定id从ioc容器中找不到对应组件,则会按照默认策略进⾏注册初始化,默认策略在DispatcherServlet.properties⽂件中配置
    在这里插入图片描述
  • DispatcherServlet.properties
    在这里插入图片描述
  • 注意:多部件解析器的初始化必须按照id注册对象(multipartResolver)
    在这里插入图片描述
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值