基本应用
基本概念
经典三层(代码架构)
- 表现层:view 试图 + controller + 数据模型(V 和 C 之间的数据传递)
- 业务层:service
- Dao 层:访问数据库
MVC 模式(代码的组织方式)
- M 模型:数据模型(POJO, VO, PO)+ 业务模型(业务逻辑)
- V 视图:即 jsp 和 html
- C 控制器:即 servlet
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
之后
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 方法完成视图渲染跳转
九大组件初始化
onRefresh 方法
策略模式 initStrategies 方法
SSM 整合
整合策略
Spring + Mybatis 整合
SSM
整合目标
数据库连接池和事务管理交给 Spring 来完成
SqlSessionFactory 对象作为单例
Mapper 代理对象交给 Spring 管理(依赖注入)