拉勾教育学习-笔记分享の"斩杀"框架篇(表现层)

557 阅读12分钟

【文章内容输出来源:拉勾教育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 流程解析

  1. 用户发送请求 --> 前端控制器 DispatcherServlet
  2. DispatcherServlet收到请求 --> 处理映射器 HandlerMapping
  3. HandlerMapping通过URL找到具体的 控制器-Handler、生成器处理器链-HandlerExecutionChain、处理器拦截器-HandlerInterceptor --> 返回DispatcherServlet
  4. DispatcherServlet调用 处理器适配器 HandlerAdapter去调用指定Handler
  5. 处理器适配器执行Handler
  6. 处理器适配器返回 ModelAndView --> DispatcherServlet
  7. DispatcherServlet请求 --> 视图解析器 ViewResolver 解析视图 --> 返回DispatcherServlet 真正视图(View)
  8. DispatcherServlet渲染视图,即 将模型数据(在 ModelAndView 对象中)填充到 request 域
  9. DispatcherServlet返回响应结果 --> 用户

P.S. ModelAndView 是SpringMVC 框架的一个底层对象,包括 Model 和 View

2.2 Spring MVC 九大组件

-A- HandlerMapping(处理器处理器映射器)
用于查找Handler,Handler可以是类或者方法
比如: 标注了@RequestMapping的每个方法就是一个Handler
Handler负责具体实际的请求处理,在请求到达后,HandlerMapping 的作用便是找到请求相应的处理器Handler 和 Interceptor.
-B- HandlerAdapter(处理器适配器)
因为 Spring MVC 中 Handler 可以是任意形式的,只要能处理请求即可。
但是把请求交给 Servlet 的时候,由于 Servlet 的方法结构都是doService(HttpServletRequest req,HttpServletResponse resp)形式的
要让固定的 Servlet 处理方法调用 Handler 来进行处理,便是 HandlerAdapter 的职责
-C- HandlerExceptionResolver(处理器异常解析器)
HandlerExceptionResolver 用于处理 Handler 产生的异常情况。
它的作用是根据异常设置ModelAndView,之后交给渲染方法进行渲染,渲染方法会将 ModelAndView 渲染成⻚面
-D- ViewResolver(视图解析器)
用于将String类型的视图名和Locale解析为View类型的视图,只有一个resolveViewName()方法。
Controller层返回的String类型视图名viewName 最终会在这里被解析成为View
View是用来渲染⻚面的,也就是说,它会将程序返回的参数和数据填入模板中,生成html文件。
ViewResolver 在这个过程主要完成两件事情:
1. 找到渲染所用的模板;
2. 找到视图的类型并填入参数
默认情况下,Spring MVC会自动为我们配置一个InternalResourceViewResolver,是针对 JSP 类型视图的。
-E- RequestToViewNameTranslator(请求转视图转译器)
从请求中获取 ViewName
因为 ViewResolver 根据ViewName 查找 View,但有的 Handler 处理完成之后,没有设置 View,也没有设置 ViewName
于是 要通过这个组件从请求中查找 ViewName
-F- LocaleResolver(区域处理器)
ViewResolver 组件的 resolveViewName 方法需要两个参数,一个是视图名,一个是 Locale
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
整型:Integer、int
字符串: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

REST -- Representational State Transfer
是一种 web 软件架构⻛格,它不是标准也不是协议,它倡导的是一个资源定位及资源操作的⻛格
互联网所有的事物都是资源,要求URL中只有表示资源的名称,没有动词。
请求分类:
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>
后台Handler
@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;
}
web.xml中配置请求方式过滤器(将特定的post请求转换为put和delete请求)
<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交互

所需jar包引入
<!--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>
前端jsp及js
<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);
			}
		})
	})
})
后台Handler
// 添加@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形式的数据

所需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>
前端表单
<%--
	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>
后台Handler
@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;

但是上述拼接参数的方法属于get请求,携带参数⻓度有限制
此时,我们可以使用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 核心功能

  1. 自定义@Controller注解并实现
  2. 自定义@Service注解并实现
  3. 自定义@Autowired注解并实现
  4. 自定义@RequestMapping注解并实现
  5. 自定义@Security注解并实现
  6. 构造重要POJO—Handler存储handler相关信息
  7. 自定义DispatcherServlet并实现
  8. 自定义组件handlerMapping并实现

Part 2 项目结构

Part 3 关注重点


整体代码如下,可以直接运行实现
相关代码

四、SpringMVC源码剖析

待跟进

五、SSM整合

待跟进

六、【加餐】

待跟进