1、SpringMVC工作流程
1.1工作流程图
1.2代码运行流程
1、客户端点击链接发送http://xxx/x请求
2、来到tomcat服务器
3、SpringMVC的前端控制器 (DispatcherServlet) 收到所有请求,
4、来看请求地址的和@RequestMapping标注的哪个匹配,找到适合的处理器(Controller)进行处理
5、前端控制器找到了目标处理类和目标方法,使用反射执行方法(Controller中对应的方法),方法是SpringMVC调用的
6、方法执行完成以后会有一个返回值,判断是否加了@ResponseBody注解,如果加了此注解,就直接将内容返回
7、没加@ResponseBody注解,拿到返回值之后,使用视图解析器进行拼串
得到完整的页面地址
8、拿到页面地址,前端控制器帮我们转发到页面
1.3DispatchServlet源码
1.3.1DispatcherServlet核心方法
请求的拦截核心调用的就是DispacherServlet类中的doDispatch()
方法
-
doDispatch()方法中的
handle()方法
执行 Controller对应的方法 -
doDispatch()方法中的
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);
//2、根据当前请求地址找到哪个Controller可以处理 将这个Controller对象封装进 mappedHandler
mappedHandler = getHandler(processedRequest);
//3、没有 Controller可以处理请求就抛异常 直接结束
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
//4、获取可以执行这个Controller的所有方法的适配器 (反射工具)
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
........
// 5、使用适配器真正的执行目标方法,最终都会返回一个ModelAndView对象
//即目标方法无论如何写,执行完毕之后的结果最后都会封装成一个ModelAndViewModelAndVie
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
........
//6、根据方法最终执行完成后封装的ModelAndView,转发到对应的页面,并且ModelAndView中的数据可以在Request域中获取
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
}
1.3.2核心方法的作用及流程
请求过来,DispatcherServlet进行拦截,调用doDispatch()方法进行处理
1、调用getHandler()方法,根据当前的请求地址去HandlerMapping中找到这个请求的处理器(Controller)
2、getHandlerAdapter()方法,根据当前的处理器(Controller)获取到能执行这个处理器方法的适配器
3、调用handle()方法,使用获取到的适配器(AnnotationMethodHandlerAdapter对象)执行目标方法,并返回一个ModelAndView对象(不管方法返回值是什么,最后都会封装为ModelAndView对象),里面封装了跳转的视图信息以及一些存的数据
4、根据ModelAndView的信息转发到具体的页面,并可以在请求域中取出ModelAndView中的数据
1.3.3getHandler()细节_去HandlerMapping找对应的处理器(Controller)
默认有两种HandlerMapping,一种是基于注解(DefaultAnnotationHandlerMapping)
,一种基于配置(Spring5中不在使用)(BeanNameUrlHandlerMapping)
,每一个处理器的方法(标注了@RequestMapping注解的方法)对应的处理器(Controller)全部保存在DefaultAnnotationHandlerMapping中的HandlerMap中,
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
//HandlerMapping中保存了每一个Controller(处理器)能处理哪些请求的信息
//保存了 每一个方法对应的Controller类 (在handlerMap属性中)
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
1.3.4getHandlerAdapter()方法,获取处理器(Controller)的可以执行方法的适配器
//传入的参数就是上一步获取的处理器类的对象即Controller对象
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
//最终获取的是AnnatationHandlerAdapter,
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
1.3.5handle()适配器执行目标方法
1.4SpringMVC九大组件及初始化
初始化:去容器中找这个组件,没有找到就用默认配置,我们使用的一般都是默认配置
共同点:九大组件全都是接口,接口就是规范!!!,我们可以自定义实现类进行扩展
//文件上传解析器
private MultipartResolver multipartResolver;
//区域信息解析器(跟国际化有关)
private LocaleResolver localeResolver;
//主题解析器
private ThemeResolver themeResolver;
//请求与处理器(Controller)的映射信息
private List<HandlerMapping> handlerMappings;
//处理器的适配器
private List<HandlerAdapter> handlerAdapters;
//处理器的异常解析器
private List<HandlerExceptionResolver> handlerExceptionResolvers;
//请求到视图的翻译器
private RequestToViewNameTranslator viewNameTranslator;
//SpringMVC允许重定向携带数据的功能
private FlashMapManager flashMapManager;
//视图解析器(前缀+后缀)
private List<ViewResolver> viewResolvers;
1.5视图解析器
2、核心
2.1@RequestMapping
2.1.2基本使用与概述
在注解中写的路径是注解的value
属性
- 前面的 / 可加可不加,但是建议加上
// /可写可不写,默认都是从当前项目开始
@RequestMapping("/hello")
public String hello(){
return "index";
}
-
一个方法只能对应一个请求
-
可以标注在类上和方法上,标在类上则为当前类中的所有方法全部都加上指定的路径
//方法和类上全都加入@RequestMapping注解 那么请求路径就是http://xxx:8080/okc/hello
@RequestMapping("/okc")
@Controller
public class UserController {
@RequestMapping("/hello")
public String hello(){
return "index";
}
}
2.1.3属性详解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
String name() default "";
//请求的路径
@AliasFor("path")
String[] value() default {};
@AliasFor("value")
String[] path() default {};
//指定请求类型 一般都是Get或者Post 不指定默认所有请求都能处理
RequestMethod[] method() default {};
---------------------------------------------------------------------------->
/*
规定请求参数
params={"k1","k2=v2","!k3","k4!=v4"}
表示:
你的url中必须全部满足下面的条件
1、必须携带k1这个参数,值无限制
2、必须携带k2这个参数 且值必须是v2
3、不能携带k3这个参数
4、可以携带k4这个参数,也可以不带,如果带了的话值必须是v4
*/
String[] params() default {};
----------------------------------------------------------------------------->
/*
规定请求头,跟params的用法相同,可以规定请求头的内容
headers={}
*/
String[] headers() default {};
-----------------------------------------------------------------------------> /*
只接受内容类型是哪种的请求,规定请求头中的Content-Type
*/
String[] consumes() default {};
--------------------------------------------------------------------------->
/*
告诉浏览器返回的内容类型是什么,给响应头中加上Content-Type
可以指定返回的是字符串是HTML代码,让浏览器解析
produces=text/html
*/
String[] produces() default {};
}
2.1.4模糊匹配
匹配规则:精确优先
?只能匹配一个字符 0个多个都不行
*可以匹配多个字符
/*/匹配一层路径
/**/匹配多层路径
@RequestMapping(value = "/shwsh1") A
@RequestMapping(value = "/shwsh?") B
@RequestMapping(value = "/shwsh*") C
访问路径为 /shwsh1 因为精确优先 A进行处理
访问路径为 /shwsh2 因为?匹配一个 比*精确 B进行处理
访问路径为 /shwsh123 只有C符合多个字符 C进行处理
----------------------------------------------------------------------------->
@RequestMapping(value = "/shwsh*/okc") D
@RequestMapping(value = "/shwsh/*/okc") E
@RequestMapping(value = "/shwsh/**/okc") F
访问路径为 /shwsh32/okc D处理
访问路径为 /shwsh/asd/okc E处理 /*/只能匹配一层路径
访问路径为 /shwsh/sd/sdds/okc F处理 /**/可以匹配多层路径
2.1.5衍生注解@GetMapping
2.1.6衍生注解@PostMapping
2.2@PathVariable(获取路径上的参数值)
- 路径上可以有占位符使用{xx},
路径上的一个{xx}代表一层路径
- 使用@PathVariable可以获取路径上占位符的值
@ResponseBody
@RequestMapping("/get/{id}/{name}")
public String zhanweifu(
//获取路径上的对应的值
@PathVariable("id")String id,
@PathVariable("name")String name){
System.out.println(id+","+name);
return id;
}
/get/1/ 这个路径匹配不上 一个占位符代表一层路径
2.3@RequestParam(获取?后面的参数值)
2.3.1基本使用与注解详解
在原来的Web开发中,我们使用request.getParameter(“key”)获取参数值,比较的麻烦,SpringMVC为我们处理了处理参数的问题
一共有两种解决方式
第一种:保证参数名和我们的方法的参数名相同,SpringMVC自动封装
比如我们的URL = http://localhost:8080/test2?user=1
参数名user和我们的方法的参数名相同,SpringMVC帮我们获取参数值并封装到形参的user属性中
@RequestMapping(value = "/test2")
public String tet2(String user){
System.out.println(user);
return user;
}
第二种:使用@RequestParam注解,value属性指定为URL上的参数名,SpringMVC也能自动封装
/*
指定获取URL中?后的那个参数的值,写参数名
String value() default "";
该参数是否是必须携带的,默认是true,如果不携带此参数报404
boolean required() default true;
如果没有携带此参数,指定一个默认值,
String defaultValue() default ""
*/
@RequestMapping(value = "/test3")
public String tet3(@RequestParam("username") String user){
System.out.println(user);
return user;
}
2.3.2@PathVariable与@RequestParam的区别
@PathVariable 获取的是路径上的参数,代表的是一层路径 想要获取参数,在URL路径上必须使用{xx}表示出来
@RequestParam 获取的是?后面的参数
http://localhost:8080/test4/{id}/{name}?user=1&age=123
@ResponseBody
@RequestMapping(value = "/test4/{id}/{name}")
public String tet4(@PathVariable("id") String id,
@PathVariable("name") String name,
@RequestParam(value = "username",) String user,
@RequestParam("age") String age) {
String data = id + name + user + age;
System.out.println(data);
return data;
}
2.4@RequestHeader
作用:获取请求头中某个key的值
用法跟@RequestParam相同
@ResponseBody
@RequestMapping("/getHeader")
public String getHeader(@RequestHeader(
//获取请求头中key为user-agent的值
value = "user-agent",
//没带时指定默认值
defaultValue = "",
//设置是否必须携带 (默认是true 必须携带否则报错)
required = false) String agent){
return agent;
}
2.5@CookieValue(获取指定的Cookie)
作用:直接获取某个Cookie的值
原生Web开发获取Cookie
//原生Web开发获取Cookie
Cookies[] cookies = request.getCookies(); //获取所有的Cookie
for(Cookie c : cookies){ //遍历所有的Cookie 根据name属性获取想要的
if("JESSIONID".equals(c.getName())){
String cookie = c.getValue();
}
.....
}
使用@CookieValue直接获取对应的Cookie
@ResponseBody
@RequestMapping("/getHeader")
public String getCookie(@CookieValue(
//获取key为JESSIONID的Cookie值
value="JESSIONID"
//不一定需要携带 (默认是true 必须携带否则报错)
required = false,
//没有携带是指定默认值
defaultValue="")String jid){
return jid;
}
2.6@RequestBody(JSON封装为对象)
- 作用:获取到
POST请求的请求体
内容 k1=v1&k2=v2… - 可以自动将请求体中的
JSON格式的字符串
转换为指定的Java对象(对象的集合也可以封装)
基本用法
1、直接获取到请求体的内容,k1=v1&k2=v2&k3=v3....
@ResponseBody
@PostMapping("/testp")
public String okc(@RequestBody String content ){
System.out.println(content);
return content;
}
2、直接封装为对象
@ResponseBody
@PostMapping("/set")
public List<User> ok(@RequestBody List<User> list){
System.out.println(list);
return list;
}
2.7参数直接封装为java对象
要求:
前端的参数名要与JavaBean的属性名完全一致
,否则无法封装,Post请求和Get请求都可以封装为对象
,- 要求Java类的属性必须要有
set方法
,否则无法封装 可以级联封装
代码示例
JavaBean
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class User {
private String username;
private Integer id;
private Double money;
private Character sex;
//有一个自定义的属性Address
private Address address;
}
------------------------------
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Address {
private String province;
private String city;
private Integer id;
}
前端
POST请求封装
<form action="/fz" method="post">
<!--每一项的name属性都必须跟对应Java类的属性名相同,否则无法封装-->
用户名<input type="text" name="username">
id<input type="text" name="id">
money<input type="text" name="money">
sex<input type="text" name="sex">
<!--级联对象属性的封装,name的值是对象.属性-->
省<input type="text" name="address.province">
市<input type="text" name="address.city">
id<input type="text" name="address.id">
<button type="submit"></button>
</form>
GET请求自动封装:直接在请求的URL的?后进行拼即可,k1=v1&k2=v2。。。这种形式,要求key必须要和属性名相同
例如下面的URL,SpringMVC也可以自动封装
<!--http://localhost:8080/fz?username=张三&id=1&address.province=江苏...-->
Controller
@ResponseBody
@RequestMapping("/fz")
//直接将对象写在参数上 无需加任何注解 自动封装
public String fz(User user){
System.out.println(user);
return user.toString();
}
2.8使用Web原生API
可以在方法的参数中声明下面几种Web原生对象
HttpServletRequest //常用
HttpServletResponse //常用
HttpSession //常用
//下面的几个不常用
java.security.Principal
Locale
InputStream
OutputStream
Reader
Writer
比如,想要在给Session中保存对象
@ResponseBody
@RequestMapping("/session")
//直接在参数列表中声明HttpSession即可
public String getWebYSAPI(HttpSession session){
session.setAttribute("username", "zhangsan");
return "Session信息已保存";
}
2.8@ResponseBody
- 主要的作用:
将返回的数据放到响应体中
- 如果返回的是对象,自动将对象转换为
JSON格式
- 如果标注了@ResponseBody注解,则此方法不会进行跳转,默认都是向页面传值
@ResponseBody
@RequestMapping("/get")
public List<User> oo(){
ArrayList<User> users = new ArrayList<>();
User user = new User("x", 1, 123d, 's', new Address("江苏省","南京市",1));
User user1 = new User("x", 1, 123d, 's', new Address("江苏省","南京市",1));
User user2 = new User("x", 1, 123d, 's', new Address("江苏省","南京市",1));
users.add(user1);
users.add(user);
users.add(user2);
return users;
}
}
测试
2.9SpringMVC_HttpEntity获取请求头+请求体的内容
@RequestMapping("/get")
public String test(HttpEntity<String> entity){ //泛型规定的是请求体的泛型
System.out.println(entity);
return "user";
}
打印出了请求体+请求头的所有内容
HttpEntity的常用方法
@ResponseBody
@RequestMapping("/get")
public String oo(HttpEntity<String> entity){
//获取请求体中的数据 entity.getBody()
/*
前端发来的是JSON数据,获取的请求体打印的也是JSON格式的数据
{"username":"x","id":1,"money":123.0,"sex":"s","address":{"province":"江苏省","city":"南京市","id":1}}
*/
System.out.println(entity.getBody());
//获取请求头中的数据 getHeaders 请求头本质上是一个Map,可以通过get方法获取指定的值
/*
[user-agent:"PostmanRuntime/7.26.10", accept:"*", postman-token:"8b65db3e-0fb5-4158-bff2-2904591c55a7", host:"localhost:8080", accept-encoding:"gzip, deflate, br", connection:"keep-alive", content-length:"108", Content-Type:"application/json;charset=UTF-8"]
*/
System.out.println(entity.getHeaders());
return "uu";
}
2.10设置响应的信息(头+体)
@ResponseBody
@RequestMapping("/test")
public ResponseEntity<String> get(){
HttpHeaders headers = new HttpHeaders();
//在请求中设置一个key为 set-Cookie 即让浏览器保存一个Cookie 原理就是这个
//可以随意的设置
headers.set("set-Cookie", "username=zhangsna");
ResponseEntity responseEntity = new ResponseEntity<String>(headers,HttpStatus.OK);
return responseEntity;
}
2.11请求头/体&响应头/体
请求头是请求对象携带的一些数据,比如Cookie,主机名,浏览器的信息等,
请求体都是存放的数据
响应头包含一些此次响应的一些数据,比如这次请求命令浏览器保存一个Cookie,那么响应头中就会加一个Set-Cookie的key,value就是k1=v1等
响应体中包含的就是此次请求返回的数据
3、将数据传递到页面
3.1Map Model ModelMap
可以使用这三种方式,默认数据都是放到request域中的,跳转到页面时可以取出数据
本质上都是一种,底层都是用BindingAwareModelMap这个类工作的
@RequestMapping("/map")
public String goOutt(Map<String,Object> map){
map.put("map","map");
return "out";
}
@RequestMapping("/model")
public String goOutt(Model model){
model.addAttribute("model","model");
return "out";
}
@RequestMapping("/modelmap")
public String goOutt(ModelMap modelMap){
modelMap.addAttribute("modelMap","modelMap");
return "out";
}
3.2ModelAndView(作为返回值)
也是在request域中存放的数据
ModelAndView可以作为方法的返回值
@RequestMapping("/modelandview")
public ModelAndView goOutt(){
//可以在构造器中传入要跳转的页面,自动拼上前后缀
ModelAndView modelAndView = new ModelAndView("out");
//放数据
modelAndView.addObject("modelAndView","modelAndView");
return modelAndView;
}
3.3给Session中保存数据的方式
使用@ModelAndView注解 只能标在类上
后续不推荐使用,给Session中放数据还是使用原生API
//value属性是一个数组,表示只要你上面的任意一种对象中存了数据,只要key指定的名,就在session中也放一份
//type属性值不管你的key是什么名,只要value是指定的类型,也给session中放一份
@SessionAttributes(value = {"map"},types = {String.class})
@Controller
public class OutPutController {
}
4、REST风格
4.1概述
一句话表示就是请求的URL都相同,使用不同的请求方式(GET POST DELETE PUT)区分不同的操作
getBook?id=1 查询图书
deleteBook?id=1 删除图书
updateBook?id=1 更新图书
addBook?id 添加图书
使用REST风格后 REST风格的URL一般这样起名:/资源名/资源标识符
简洁的URL提交请求,以请求方式区分对资源操作
/book/1 GET请求方式 ----查询
/book/1 PUT请求方式 ----修改
/book/1 DELETE请求方式 ----删除
/book POST请求方式 ---添加
4.2存在的问题
在页面上只能发起两种方式的请求,GET和POST请求 使用过滤器解决问题 ,核心就是将POST请求改为PUT或者DELETE请求
后续开发很少使用
5、文件上传
5.1开发步骤
1、导入依赖
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>1.2</version>
</dependency>
2、配置Bean
@Configuration
public class FileUpLoadConfig {
//配置文件上传MultipartResolver 注意:bean的id必须是multipartResolver
//初始化时SpringMVC会从IOC容器中获取此Bean id就是multipartResolver
@Bean
public MultipartResolver multipartResolver() {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
//设置上传文件最大值
multipartResolver.setMaxUploadSize(1024 * 1024 * 20);
//设置默认编码
multipartResolver.setDefaultEncoding("utf-8");
...设置属性
return multipartResolver;
}
}
3、html
<!--文件上传方式使用post请求 enctype指定为multipart/form-data-->
<form action="/upload" method="post" enctype="multipart/form-data">
上传文件<input type="file" name="headerimg">
<input type="submit">
</form>
4、Controller
@Controller
public class FileUploadController {
@ResponseBody
//配置文件上传解析器
@RequestMapping("/upload")
public String upload(Model model,
//文件信息封装在了MultipartFile里
@RequestParam("headerimg")
MultipartFile file) throws IOException { //文件的源名字
System.out.println("文件的名字getOriginalFilename"+file.getOriginalFilename());
//指定文件的保存路径
file.transferTo(new File("D:\\desk\\upfile\\"+file.getOriginalFilename()));
model.addAttribute("msg", "文件上传成功");
return "上传成功";
}
}
5.2多文件上传
前两步跟单文件上传都一样
第三步
<form action="/upload" method="post" enctype="multipart/form-data">
用户头像: <input type="file" name="headerimg">
用户的图片: <input type="file" name="userimg">
用户的合照: <input type="file" name="userandimg">
<input type="submit">
第四步
@Controller
public class FileUploadController {
@ResponseBody
//指定多个MultipartFile即可
@RequestMapping("/upload")
public String upload( @RequestParam("headerimg") MultipartFile file,
@RequestParam("userimg") MultipartFile userimg, @RequestParam("userAndimg") MultipartFile userAndimg){
...文件保存
}
}
6、拦截器(HandlerInterceptor)
6.1概述及源码
- SpringMVC提供了拦截器机制,允许目标方法之前进行一些拦截工作,或者在目标方法之后进行一些其他处理
- SpringMVC的拦截器是一个
接口HandlerInterceptor
,实现拦截功能要实现此接口,并配置拦截那些方法以及拦截前后的操作等
接口的源码
public interface HandlerInterceptor {
//在目标方法之前调用,返回值是boolean,返回true表示执行目标方法(放行),返回false表示不放行
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
//在目标方法执行之后调用,
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
//在请求整个完成之后来到目标页面后调用 注意:只要preHandle放行了,即使是来到了错误页面此方法也会被执行(类似finally块)
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}
}
6.2拦截器使用
1.实现HandlerInterceptor接口
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//....一些处理
return true; //true放行 false不放行(在此方法中做一些判断后续是否放行)
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
//....一些处理
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//....一些处理
}
}
2.注册拦截器并在SpringMVC中配置
@Configuration
public class ApplicationConfig implements WebMvcConfigurer {
//注册拦截器并设置要拦截那些请求
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册自定义的拦截器 //配置要拦截哪些请求(默认拦截所有还可以进行排除策略)
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/inter");
}
}
6.3运行流程
①preHanler--(如果放行)-->②目标方法--->③postHandler--->④来到页面--->⑤afterCompleion
| 注:只要放行之后,不管来到什么页面afterCompleion一定会执行
或
|
①preHanler--(不放行)--->后续的任何操作都不再执行
6.4多拦截器及其工作流程
1.多拦截器编码实现
继续自定义HandlerInterceptor的实现类然后在配置类中注册并配置即可
2.多拦截器的运行流程
正常流程
- 拦截器的preHandler按照注册的顺序执行
- 拦截器的postHandler按照注册的逆序执行,
在页面渲染之前执行
- 拦截器的afterCompletion按照注册的逆序执行
已经放行了的拦截器,即使后面的拦截器不放行,但是已经放行的afterCompletion一定会执行
6.5拦截器源码
//在执行目标方法之前,会先执行拦截器的preHandler方法,只要有一个拦截器返回false(不放行),整个方法链直接return ,返回false的拦截器跳到afterCompetition执行
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
执行所有拦截器的preHandle的源码
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
//如果某一个拦截器不放行,直接进入AfterCompletion方法,执行前面放行的拦截器的
//afterCompletion方法
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
//直接return false
return false;
}
//记录放行的拦截器的索引 便于后续执行放行的拦截器的afterCompletion方法
this.interceptorIndex = i;
}
}
return true;
}
执行所有拦截器的postHandle的源码
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
throws Exception {
//获取所有拦截器
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
//倒序遍历,逆序执行PostHandler(所有的拦截器都放行才会走到这一步)
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}
afterCompletion总会执行
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
//倒序遍历拦截器,逆序执行preHandler放行的拦截器的afterCompletion方法
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
}
拦截器的流程总结
单拦截器(因为SpringMVC自带一个拦截器,这里只考虑自定义的拦截器)的执行流程,
执行preHandler方法,①如果返回false,流程直接结束
②如果返回true,接着往下执行目标方法,然后执行postHandler方法,然后 执行页面的渲染,最后执行afterCompletion方法
多个拦截器
根据拦截器的注册顺序正序执行preHandler方法,
①如果所有的拦截器都放行(返回true),那么就执行目标方法,然后倒序执行所有拦截器的postHandler方法,然后渲染页面,最后倒序执行所有拦截器的afterCompletion方法
②如果有一个拦截器不放行,那么就直接执行它前面的所有放行的拦截器的afterCompletion方法,然后返回false,方法链直接结束,它后面的所有拦截器的所有方法都不执行。
6.6拦截器中注入Bean为null
7.异常处理
可以定制出现异常时的页面。。。
8.SpringMVC运行流程总结
1、所有请求被前端控制器DispatcherServlet拦截,调用doDispatch方法进行处理
2、在HandlerMapping中寻找该请求有处理器(Controller)处理,返回一个处理器执行链(包含拦截器)
3、根据当前的处理器获取对应的HandlerAdapter(适配器)
4、执行拦截器的preHandle方法
5、适配器执行目标方法,并返回ModelAndView
进行参数处理 。 。 。
6、执行拦截器的postHandle方法
7、处理结果(页面渲染流程)
8、执行拦截器的afterCompletion方法
9、整合Spring
9.1为什么整合?
SpringMVC和Spring整合目的:分工明确
- SpringMVC的配置文件就来配置和网站转发逻辑以及网站功能有关的,
(视图解析器,拦截器,文件上传解析器)
- Spring的配置文件用来配置和
业务有关的(事务处理 数据源 xxx.... )
9.2整合
SpringMVC和Spring分容器
- Spring的容器除了Controller不扫其他组件全扫
- SpringMVC的容器
只扫描控制器(Controller)
9.3父子容器
如果有两个容器,一个Spring的容器,一个SpringMVC的容器,Spring框架的默认将Spring的容器ApplicationContext作为父容器,SpringMVC的容器作为子容器,子容器(MVC的容器)可以自动装配父容器(Spring的容器)中的Bean,但是父容器不能装配子容器中的Bean,
例如子容器中的Controller可以自动装配父容器中的Service,但是父容器中的Service不能装配子容器中的Controller等