【文章内容输出来源:拉勾教育Java高薪训练营】
--- 所有脑图均本人制作,未经允许请勿滥用 ---
!! 整体重心放在SpringMVC的源码剖析中,同时结合手写实现充分"得之以渔"
一、SpringMVC快速回顾
Part 1 - 简介
1.1 MVC 体系
我们的开发架构⼀般都是基于两种形式,⼀种是 C/S 架构,也就是客户端/服务器;另⼀种是 B/S 架构
,也就是浏览器服务器。
B/S 架构中,系统标准的三层架构包括:表现层、业务层、持久层。
表现层 (展示层+控制层)
也就是我们常说的web 层。
接收客户端请求,向客户端响应结果,通常客户端使⽤http 协议请求web 层,web 需要接收 http 请求,完成 http 响应。
业务层
也就是我们常说的 service 层。负责业务逻辑处理。
持久层 (数据层+数据访问层)
也就是我们是常说的 dao 层。
数据库是对数据进⾏持久化的载体;
数据访问层是业务层和持久层交互的接⼝
web 层依赖业务层,但是业务层不依赖 web 层
业务层在业务处理时可能会依赖持久层,如果要对数据持久化需要保证事务⼀致性。(也就是我们说的, 事务应该放到业务层来控制)
1.2 MVC 设计模式
SpringMVC 全名叫 Spring Web MVC,是一种基于 Java 的实现 MVC 设计模型的请求驱动类型的轻量级Web 框架,属于 SpringFrameWork 的后续产品.
【总之】Spring MVC和Struts2一样,都是为了解决表现层问题的web框架,它们都是基于 MVC 设计模式的。而这些表现层框架的主要职责就是处理前端HTTP请求
【Spring MVC本质】可以认为是对servlet的封装
Part 2 - 工作流程
下方图片来源:拉勾java高薪训练营
2.1 流程解析
- 用户发送请求 --> 前端控制器
DispatcherServlet - DispatcherServlet收到请求 --> 处理映射器
HandlerMapping - HandlerMapping通过URL找到具体的 控制器-
Handler、生成器处理器链-HandlerExecutionChain、处理器拦截器-HandlerInterceptor--> 返回DispatcherServlet - DispatcherServlet调用 处理器适配器
HandlerAdapter去调用指定Handler - 处理器适配器执行Handler
- 处理器适配器返回
ModelAndView--> DispatcherServlet - DispatcherServlet请求 --> 视图解析器
ViewResolver解析视图 --> 返回DispatcherServlet 真正视图(View) - DispatcherServlet渲染视图,即 将模型数据(在 ModelAndView 对象中)填充到 request 域
- DispatcherServlet返回响应结果 --> 用户
P.S. ModelAndView 是SpringMVC 框架的一个底层对象,包括 Model 和 View
2.2 Spring MVC 九大组件
-A- HandlerMapping(处理器处理器映射器)
比如: 标注了
@RequestMapping的每个方法就是一个HandlerHandler负责具体实际的请求处理,在请求到达后,HandlerMapping 的作用便是找到请求相应的处理器Handler 和 Interceptor.
-B- HandlerAdapter(处理器适配器)
但是把请求交给 Servlet 的时候,由于 Servlet 的方法结构都是
doService(HttpServletRequest req,HttpServletResponse resp)形式的要让固定的 Servlet 处理方法调用 Handler 来进行处理,便是 HandlerAdapter 的职责
-C- HandlerExceptionResolver(处理器异常解析器)
它的作用是根据异常设置ModelAndView,之后交给渲染方法进行渲染,渲染方法会将 ModelAndView 渲染成⻚面
-D- ViewResolver(视图解析器)
resolveViewName()方法。Controller层返回的String类型视图名viewName 最终会在这里被解析成为View
View是用来渲染⻚面的,也就是说,它会将程序返回的参数和数据填入模板中,生成html文件。
ViewResolver 在这个过程主要完成两件事情:
1. 找到渲染所用的模板;
2. 找到视图的类型并填入参数
默认情况下,Spring MVC会自动为我们配置一个InternalResourceViewResolver,是针对 JSP 类型视图的。
-E- RequestToViewNameTranslator(请求转视图转译器)
因为 ViewResolver 根据ViewName 查找 View,但有的 Handler 处理完成之后,没有设置 View,也没有设置 ViewName
于是 要通过这个组件从请求中查找 ViewName
-F- LocaleResolver(区域处理器)
LocaleResolver 用于从请求中解析出 Locale,比如中国 Locale 是 zh-CN,用来表示一个区域。
这个组件也是 i18n 的基础
-G- ThemeResolver(主题处理器)
主题是样式、图片及它们所形成的显示效果的集合
Spring MVC 中一套主题对应一个 properties文件,里面存放着与当前主题相关的所有资源,如图片、CSS样式等
-H- MultipartResolver(多元处理器)
将普通的请求包装成 MultipartHttpServletRequest 来实现
MultipartResolver 的作用就是封装普通的请求,使其拥有文件上传的功能
-I- FlashMapManager(刷新集管理器)
比如在处理用户订单时候,为了避免重复提交,可以处理完post请求之后重定向到一个get请求,这个get请求可以用来显示订单详情之类的信息
Part 3 - 参数绑定
http协议是“超文本传输协议”————"超文本"!"文本"!
所以从web端传出来的参数都是以 字符的形式
参数绑定 —— 取出参数值绑定到handler方法的形参上
3.1 默认支持 Servlet API 作为方法参数
HttpServletRequest、HttpServletResponse、HttpSession等原生servlet对象时直接在handler方法中形参声明使用即可
// http://localhost:8080/test/handler?id=1
public ModelAndView handler(HttpServletRequest request, HttpServerletResponse response, HttpSession session) {
String id = request.getParameter("id");
Date date = new Date();
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("id", id);
modelAndView.addObject("date", date);
modelAndView.setViewName("success");
return modelAndView;
}
3.2 绑定简单类型参数
推荐使用包装数据类型,因为基础数据类型不可以为null
字符串:String
单精度:Float、float
双精度:Double、double
布尔型:Boolean、boolean
对于布尔类型的参数,请求的参数值为true或false。或者1或0
当形参参数名和传递参数名不一致时可以使用 @RequestParam 注解进行手动映射
// http://localhost:8080/test/handler?ids=1&flag=0
@RequestMapping("/handler")
public ModelAndView handler(@RequestParam("ids") Integer id,Boolean flag) {
ModelAndView modelAndView = new ModelAndView();
System.out.println(id + flag);
modelAndView.setViewName("success");
return modelAndView;
}
3.3 绑定Pojo类型参数
// http://localhost:8080/test/handler?id=1&username='archie'
@RequestMapping("/handler")
public ModelAndView handler(User user) {
ModelAndView modelAndView = new ModelAndView();
System.out.println(user.getId() + user.getUserName);
modelAndView.setViewName("success");
return modelAndView;
}
3.4 绑定Pojo包装对象参数
class User {
private Integer id;
private String userName;
// ...省略get/set方法
}
class UserVO {
private mail;
private phone;
/* 嵌套User */
private User user;
// ...省略get/set方法
}
传参参数名和pojo属性保持一致,如果不能够定位数据项,那么通过属性名 + "." 的方式进一步锁定数据
// http://localhost:8080/test/handler?user.id=1&user.username='archie'
@RequestMapping("/handler")
public ModelAndView handler(UserVO userVO) {
ModelAndView modelAndView = new ModelAndView();
System.out.println(userVO.getUser.getId() + userVO.getUser().getUserName);
modelAndView.setViewName("success");
return modelAndView;
}
3.5 绑定日期类型参数(需要配置自定义类型转换器)
前端jsp
<fieldset>
<p>SpringMVC接收日期类型参数</p>
<a href="/test/handler?birthday=2020-12-10">点击测试</a>
</fieldset>
handler
@RequestMapping("/handler")
public ModelAndView handler(Date birthday) {
ModelAndView modelAndView = new ModelAndView();
System.out.println(birthday);
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 trueDate = simpleDateFormat.parse(source);
return trueDate;
} catch(ParseException e) {
e.printStackTrace();
}
return null;
}
注册自定义类型转换器
<!--自动注册最合适的处理器映射器/处理器适配器-->
<mvc:annotation-driven conversion-service="conversionServiceBean"/>
<!--注册自定义类型转换器-->
<bean id="conversionServiceBean" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.archie.test.converter.DateConverter"/>
</set>
</property>
</bean>
Part 4 - Restful风格
4.1 何为RESTful
是一种 web 软件架构⻛格,它不是标准也不是协议,它倡导的是一个资源定位及资源操作的⻛格
互联网所有的事物都是资源,要求URL中只有表示资源的名称,没有动词。
- 传统URL: http://localhost:8080/user/queryUserById.action?id=1
定义了动作,参数直接锁定了额操作对象 - REST风格URL:http://localhost:8080/user/3
只锁定要操作的资源对象是id为3的那个资源
至于动作根据不同请求方式区分
get 获取资源
post 新建资源
put 更新资源
delete 删除资源
/account/1 HTTP GET :得到 id = 1 的 account
/account/1 HTTP DELETE:删除 id = 1 的 account
/account/1 HTTP PUT:更新 id = 1 的 account
4.2 使用@PathVariable实现RESTful
<!-- 获取id为5的用户 -->
<a href="/test/handle/15">REST get请求</a>
<!-- 新增用户,用户名为Archie -->
<form method="post" action="/test/handle">
<input type="text" name="username" value="Archie" />
<input type="submit" name="提交REST post请求" />
</form>
<!-- 更新id为5的用户,名称改为Mick -->
<form method="post" action="/test/handle/15/Mick">
<input type="hidden" name="_method" value="put" />
<input type="submit" name="提交REST put请求" />
</form>
<!-- 删除id为5的用户 -->
<form method="post" action="/test/handle/15">
<input type="hidden" name="_method" value="delete" />
<input type="submit" name="提交REST delete请求" />
</form>
@RequestMapping(value = "/handle/{id}", method = {RequestMethod.GET})
public ModelAndView handleGet(@PathVariable("id") Integer id) {
ModelAndView modelAndView=new ModelAndView();
System.out.println(id);
return modelAndView;
}
@RequestMapping(value = "/handle", method = {RequestMethod.POST})
public ModelAndView handlePost(String username) {
ModelAndView modelAndView=new ModelAndView();
testService.save(username);
return modelAndView;
}
@RequestMapping(value = "/handle/{id}/{username}", method = {RequestMethod.PUT})
public ModelAndView handlePut(@PathVariable("id") Integer id, @PathVariable("username") String userName) {
ModelAndView modelAndView=new ModelAndView();
testService.update(id, userName);
return modelAndView;
}
@RequestMapping(value = "/handle/{id}", method = {RequestMethod.DELETE})
public ModelAndView handleDelete(@PathVariable("id") Integer id) {
ModelAndView modelAndView=new ModelAndView();
testService.delete(id);
return modelAndView;
}
<filter>
<filter-name>hiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>hiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Part 5 - Ajax JSON交互
1. 前端到后台
前端ajax发送json格式字符串,后台直接接收为pojo参数,使用注解@RequstBody
2. 后台到前端
后台直接返回pojo对象,前端直接接收为json对象或者字符串,使用注解@ResponseBody
5.1 何为JSON
Json是一种与语言无关的数据交互格式,就是一种字符串.
只是用特殊符号
{}内表示对象、
[]内表示数组、
""内是属性或值、
:表示后者是前者的值
5.2 @ResponseBody注解
将controller的方法返回的对象通过适当的转换器转换为指定的格式之后,写入到response对象的body区
通常用来返回JSON数据或者是XML数据。
在使用此注解之后不会再走视图处理器,而是直接将数据写入到输入流中,他的效果等同于通过response对象输出指定格式的数据
5.3 分析SpringMVC使用JSON交互
<!--json数据交互所需jar,start-->
<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>
<div>
<h2>Ajax json交互</h2>
<fieldset>
<inputtype="button"id="ajaxBtn"value="ajax提交"/>
</fieldset>
</div>
$(function () {
$("#ajaxBtn").bind("click", function() {
$.ajax({
url: '/test/handle',
type: 'POST',
data: '{"id":"1","username":"Arice"}',
contentType: 'application/json;charset=utf-8',
dataType: 'json',
success: function (data) {
alert(data.name);
}
})
})
})
// 添加@ResponseBody之后,不再走视图解析器那个流程,而是等同于response直接输出数据
@RequestMapping(value = "/handle")
public @ResponseBody handle(@RequesetBody User user) {
user.setName("Mick");
return user;
}
二、SpringMVC高级技术
Part 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的创建和销毁;变量的创建、销毁和修改等。 - 拦截器(Interceptor):
是SpringMVC、Struts等表现层框架自己的,不会拦截jsp/html/css/image的访问等,只会拦截访问的控制器方法(Handler) - 总结:
serlvet、filter、listener是配置在web.xml中的,而interceptor是配置在表现层框架自己的配置文件中的
1.2 拦截器执行流程
1.3 多个拦截器的执行流程
待跟进
Part 2 处理multipart形式的数据
<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>
<%--
1 method="post"
2 enctype="multipart/form-data"
3 type="file"
--%>
<form method="post" action="/test/upload" enctype="multipart/form-data">
<input type="file" name="uploadFile" />
<input type="submit" name="上传" />
</form>
@RequestMethod("upload")
public Stirng upload(MultipartFile uploadFile, HttpServletRequest request) throws IOException{
// 文件原名 如xxx.jpg
String originalFileName = uploadFile.getOriginalFileName();
// 获取文件的扩展名
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(realPaht + "/" + datePath);
if(!floder.exists()) {
floder.mkdirs();
}
uploadFile.transferTo(new File(floder, newName));
return "success";
}
Part 3 控制器中处理异常
@ControllerAdvice
public class GlobalExceptionResolver {
@ExceptionHandler(ArithmeticException.class)
public ModelAndView handleException(ArithmeticException exception, HttpServletRequest response) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("msg", exception.getMessage());
modelAndView.setViewName("error");
return modelAndView;
}
}
Part 4 基于Flash属性的跨重定向请求数据传递
转发
url不变/参数不丢失/是同一个请求
A 向 B 借100元,B没有,但B想办法凑到了100给 A
重定向
url会变/参数会丢失/非同一个请求
A 向 B 借100元,B没有,直接让 A 去找别人借
return"redirect:handle01?name="+name;
此时,我们可以使用SpringMVC提供的flash属性机制,向上下文中添加flash属性
框架会在session中记录该属性值,当跳转到⻚面之后框架会自动删除flash属性
⻓度和安全性都得到了保障
@RequestMapping("/handleRedirect")
public String handleRedirect(String name,RedirectAttributes redirectAttributes) {
// addFlashAttribute方法设置了一个flash类型属性,
// 该属性会被暂存到session中,在跳转到⻚面之后该属性销毁
redirectAttributes.addFlashAttribute("name",name);
return"redirect:handle01";
}
三、手写SpringMVC框架
Part 1 核心功能
- 自定义
@Controller注解并实现 - 自定义
@Service注解并实现 - 自定义
@Autowired注解并实现 - 自定义
@RequestMapping注解并实现 - 自定义
@Security注解并实现 - 构造重要POJO—Handler存储handler相关信息
- 自定义DispatcherServlet并实现
- 自定义组件handlerMapping并实现
Part 2 项目结构
Part 3 关注重点
整体代码如下,可以直接运行实现
相关代码
四、SpringMVC源码剖析
待跟进
五、SSM整合
待跟进
六、【加餐】
待跟进