MVC 模式(代码的组织方式)
Spring MVC 属于表现层
位于 Spring 架构图中的 Web 模块,本质上是对 servlet 的封装
工作流程
- 用户发送请求到前端控制器
- 前端控制器到处理器映射器查询请求对应的 Handler
- 处理器映射器返回处理器执行链
- 前端控制器向处理器适配器请求执行 Handler
- 处理器适配器请求处理器执行 Handler
- 处理器返回 ModelAndView
- 处理器适配器返回 ModelAndView
- 前端控制器向视图解析器请求解析视图
- 视图解析器返回 View 对象
- 前端控制器渲染视图
- 前端控制器响应用户
九大组件
- HandlerMapping 处理器映射器
- HandlerAdapter 处理器适配器
- HandlerExceptionResolver 根据异常设置 ModelAndView
- ViewResolver 视图解析器,用于将视图名解析为视图
- RequestToViewNameTranslator 用于查找视图名
- LocaleResolver 用于国际化
- ThemeResolver 用于解析主题
- MultipartResolver 用于上传请求
- FlashMapManager 用于不想把参数写进 URL
请求参数绑定
Servlet API
DemoController.java
/** * 对原生servlet api的支持 * url:/demo/handle02?id=1 * 如果要在SpringMVC中使用servlet原生对象,直接在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; } 复制代码
success.jsp
<%@ page contentType="text/html; charset=utf-8" language="java" %> <html> <head> <title>Success</title> </head> <body> 跳转成功!服务器时间:${date} </body> </html> 复制代码
springmvc.xml
<!--配置springmvc的视图解析器--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> 复制代码
绑定简单类型参数
/* * 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 包装类型参数
/**
* 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;
}
复制代码
绑定日期类型参数
需要配置自定义类型转换器,实现Converter接口
springmvc.xml
<!--自动注册最合适的处理器映射器,处理器适配器(调用handler方法)-->
<mvc:annotation-driven conversion-service="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>
复制代码
DemoController.java
/** * 绑定日期类型参数 * 定义一个 SpringMVC 的类型转换器接口,扩展实现接口接口,注册你的实现 */ @RequestMapping("/handle06") public ModelAndView handle06(Date birthday) { Date date = new Date(); ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("date", date); modelAndView.setViewName("success"); return modelAndView; } 复制代码
REST 风格支持
什么是 REST 风格?
之前
http://localhost:8080/user/queryUserById_action?id=3
之后
http://localhost:8080/user/3
get(查询),post(增加),put(更新),delete(删除)
先锁定资源,再根据请求方式不同决定操作
直观体验:传递参数方式的变化,参数可以在 uri 中
@PathVariable 注解
/* * restful get * url: /demo/handle/15 */ @RequestMapping(value = "/handle/{id}", method = {RequestMethod.GET}) public ModelAndView handleGet(@PathVariable("id") Integer id) { Date date = new Date(); ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("date", date); modelAndView.setViewName("success"); return modelAndView; } /* * restful post * url: /demo/handle */ @RequestMapping(value = "/handle", method = {RequestMethod.POST}) public ModelAndView handlePost(String username) { Date date = new Date(); ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("date", date); modelAndView.setViewName("success"); return modelAndView; } /* * restful put * url: /demo/handle/15/lisi */ @RequestMapping(value = "/handle/{id}/{name}", method = {RequestMethod.PUT}) public ModelAndView handlePut(@PathVariable("id") Integer id, @PathVariable("name") String username) { Date date = new Date(); ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("date", date); modelAndView.setViewName("success"); return modelAndView; } /* * restful delete * url: /demo/handle/15 */ @RequestMapping(value = "/handle/{id}", method = {RequestMethod.DELETE}) public ModelAndView handleDelete(@PathVariable("id") Integer id) { Date date = new Date(); ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("date", date); modelAndView.setViewName("success"); return modelAndView; } 复制代码
AJAX 支持
引入 Jackson 依赖
@RequestBody 注解
@ResponseBody 注解
将 Handler 方法返回的对象转换为指定的格式之后,写入到 response 对象的 body 区,通常用来返回 JSON 数据或者是XML数据。在使用此注解之后不会再走视图处理器,而是直接将数据写入到输出流中,他的效果等同于通过 response 对象输出指定格式的数据。
@RequestMapping("/handle07") // 添加@ResponseBody之后,不再走视图解析器那个流程,而是等同于response直接输出数据 @ResponseBody public User handle07(@RequestBody User user) { // 业务逻辑处理,修改name为张三丰 user.setName("张三丰"); return user; } 复制代码
高级技术
拦截器
单个拦截器
方法 | 操作 |
---|---|
CustomInterceptor::preHandle | 拦截,若返回 true 则放行 |
HandlerAdaptor::handle | 处理器适配器处理请求 |
CustomInterceptor::postHandle | 拦截 |
DispatcherServlet::render | 前端控制器渲染视图 |
CustomInterceptor::afterCompletion | 拦截 |
多个拦截器
方法 | 操作 |
---|---|
Interceptor1::preHandle | 拦截器1进行拦截 |
Interceptor2::preHandle | 拦截器2进行拦截 |
HandlerAdaptor::handle | 处理器适配器处理请求 |
Interceptor2::postHandle | 拦截器2进行拦截 |
Interceptor1::postHandle | 拦截器1进行拦截 |
DispatcherServlet::render | 前端控制器渲染视图 |
Interceptor2::afterCompletion | 拦截器2进行拦截 |
Interceptor1::afterCompletion | 拦截器1进行拦截 |
自定义拦截器
实现 HandlerInterceptor 接口
public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 权限校验 } } 复制代码
处理 Multipart 形式的数据
配置MultipartResolver,id固定为multipartResolver
<!--多元素解析器,id固定为multipartResolver--> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!--设置上传文件大小上限,单位是字节,-1代表没有限制也是默认的--> <property name="maxUploadSize" value="5000000"/> </bean> 复制代码
异常处理
@ControllerAdvice
@ExceptionHandler
// 可以让我们优雅的捕获所有Controller对象handler方法抛出的异常 @ControllerAdvice public class GlobalExceptionResolver { @ExceptionHandler(ArithmeticException.class) public ModelAndView handle(ArithmeticException exception, HttpServletResponse response) { ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("msg", exception.getMessage()); modelAndView.setViewName("error"); return modelAndView; } } 复制代码
手写框架
实现思路
- Tomcat 加载 web.xml
- 前端控制器加载 spring.xml 配置文件,开启扫描
- 包扫描,注解扫描
- IOC 容器进行 bean 的初始化和依赖处理
- Spring MVC 相关组件初始化,建立 url 和 method 之间的映射
- HandlerMapping 等待请求进来,处理请求
源码剖析
前端控制器 DispatcherServlet 的代码结构
DispatcherServelt extends FrameworkServlet extends HttpServletBean extends HttpServlet
FrameworkServlet 的 doGet 和 doPost 调用 doService 抽象方法
DispatcherServlet 的 doService 调用 doDispatch 方法
核心方法为 doDispatch
重要时机点分析
Handler方法的执行时机
页面渲染时机(打断点并观察调用栈)
核心步骤
SpringMVC处理请求的流程即为 DispatcherServlet::doDispatch 方法的执行过程
1. 调用getHandler()获取到能够处理当前请求的执行链 HandlerExecutionChain(Handler + 拦截器) 2. 调用getHandlerAdapter();获取能够执行1 中 Handler 的适配器 3. 适配器调用 Handler 执行 ha.handle(总会返回一个ModelAndView对象) 4. 调用 processDispatchResult 方法完成视图渲染跳转 复制代码
原文
https://juejin.im/post/5ef6c702e51d45348255088a
本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » Spring MVC 学习笔记