一、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)