目录
拦截器:是Spring框架提供的核心功能之一,主要用来拦截用户的请求,在指定方法前后,根据业务需要执行预先设定的代码:
自定义拦截器
统一数据格式,要包含状态码,错误信息编辑
出现针对String类型的错误
为什么要统一
统一功能来实现捕捉到异常。编辑
@ControllerAdvice源码分析
数据结构面试:HashMap,ConcurrentHashMap,ThreadLocal要看(建议看源码)
网络:TCP,UDP,HTTP
并发编程:线程创建方式,线程的状态,锁,synchronized,volatile
数据库 基本操作,关键字,
JVM-内存结构,GC
Linux
引入拦截器的原因。
拦截器:是Spring框架提供的核心功能之一,主要用来拦截用户的请求,在指定方法前后,根据业务需要执行预先设定的代码:
(指定在哪些方法前后执行)
JDK17把之前Java的包都换为Jakarta。
指定方法:——指我们登录验证的某些方法(比如图书的增删改查),怎样可以不用动太多代码,实现这个功能
预先设定的代码:对用户是否登录进行验证
拦截器的实现分为两步:(作用维度以url为维度)
1.定义一个拦截器
2.把拦截器注册到项目中
自定义拦截器:实现HandlerInterceptor接口,并重写所有方法
拦截器的定义
自定义拦截器
/* 一级路径 能匹配/user,/book,/login,不能匹配/user/login
/** 任意级路径 能匹配 /user,/user/login,/book
/book/* /book下的一级路径 能匹配/book/addBook,不能匹配/book/addBook
/book/** /book下的任意级路径 能匹配/book,/book/addBook,/book/addBook/2...。
正常工作方式
用户调用->(拦截器)->控制器层(Controller)->调用服务层(Service)->数据持久层(Mapper)->数据库
1.添加拦截器后, 执⾏Controller的⽅法之前, 请求会先被拦截器拦截住. 执⾏ preHandle() ⽅法,
这个⽅法需要返回⼀个布尔类型的值. 如果返回true, 就表⽰放⾏本次操作, 继续访问controller中的
⽅法. 如果返回false,则不会放⾏(controller中的⽅法也不会执⾏).
2. controller当中的⽅法执⾏完毕后,再回过来执⾏ postHandle() 这个⽅法以及
afterCompletion() ⽅法,执⾏完毕之后,最终给浏览器响应数据
DispatcherServlet(Dispatcher调度器,servlet生命周期)
源码环节
适配器模式和门面模式区别
1.适配器模式:是适配两者之间的差异(为了补救设计上的缺陷,应用这种模式也是无心之举),门面模式是统合了底层系统(我认为一个像是七巧板把两个不同的连接起来,门面模式就像是给他装了一个套,它里面不变,只是说可以调用他们两个)我们通过外面的套来调用里面。
统一数据格式,要包含状态码,错误信息
supports⽅法: 判断是否要执⾏beforeBodyWrite⽅法. true为执⾏, false不执⾏. 通过该⽅法可以 选择哪些类或哪些⽅法的response要进⾏处理, 其他的不进⾏处理.
beforeBodyWrite⽅法: 对response⽅法进⾏具体操作处理
出现针对String类型的错误
快速⼊⻔ 统⼀的数据返回格式使⽤ @ControllerAdvice 和 ResponseBodyAdvice 的⽅式实现 @ControllerAdvice 表⽰控制器通知类 添加类 ResponseAdvice , 实现 ResponseBodyAdvice 接⼝, 并在类上添加 @ControllerAdvice 注解 import com.example.demo.model.Result; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; @ControllerAdvice public class ResponseAdvice implements ResponseBodyAdvice { @Override public boolean supports(MethodParameter returnType, Class converterType) { return true; } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { return Result.success(body); } }
解决方法:
public class TestController { @RequestMapping("/t1") public String t1(){ return "t1"; } @RequestMapping("/t2") public boolean t2(){ return true; } @RequestMapping("/t3") public Integer t3(){ return 200; } } 解决⽅案: import com.example.demo.model.Result; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; @Slf4j @ControllerAdvice public class ResponseAdvice implements ResponseBodyAdvice { private static ObjectMapper mapper = new ObjectMapper(); @Override public boolean supports(MethodParameter returnType, Class converterType) { return true; } @SneakyThrows @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { //如果返回结果为String类型, 使⽤SpringBoot内置提供的Jackson来实现信息的序列化 if (body instanceof String){ return mapper.writeValueAsString(Result.success(body)); } return Result.success(body); } }
为什么会有这样的错误呢?
SpringMVC默认会注册⼀些⾃带的 HttpMessageConverter (从先后顺序排列分别为
ByteArrayHttpMessageConverter ,
StringHttpMessageConverter , SourceHttpMessageConverter ,
SourceHttpMessageConverter , AllEncompassingFormHttpMessageConverter )
AllEncompassingFormHttpMessageConverter 会根据项⽬依赖情况 添加对应的
HttpMessageConverter
在依赖中引⼊jackson包后,容器会把 MappingJackson2HttpMessageConverter ⾃动注册到
messageConverters 链的末尾.
Spring会根据返回的数据类型, 从 messageConverters 链选择合适的
HttpMessageConverter .
当返回的数据是⾮字符串时, 使⽤的 MappingJackson2HttpMessageConverter 写⼊返回对象.
当返回的数据是字符串时, StringHttpMessageConverter 会先被遍历到,这时会认为
StringHttpMessageConverter 可以使⽤
在 ((HttpMessageConverter) converter).write(body, selectedMediaType,
outputMessage) 的处理中, 调⽤⽗类的write⽅法
由于 StringHttpMessageConverter 重写了addDefaultHeaders⽅法, 所以会执⾏⼦类的⽅法
然⽽⼦类 StringHttpMessageConverter 的addDefaultHeaders⽅法定义接收参数为String, 此
时t为Result类型, 所以出现类型不匹配"Result cannot be cast to java.lang.String"的异常
最终
1. ⽅便前端程序员更好的接收和解析后端数据接⼝返回的数据
2. 降低前端程序员和后端程序员的沟通成本, 按照某个格式实现就可以了, 因为所有接⼝都是这样返回
的.
3. 有利于项⽬统⼀数据的维护和修改.
4. 有利于后端技术部⻔的统⼀规范的标准制定, 不会出现稀奇古怪的返回内容.
Spring的代码在书写统一的过程中,不按照顺序。
捕捉异常的时候,看他报什么异常,最贴近哪个,那么就是哪个
不加ResponseBody -她就会把下面那个异常当成一条数据
package com.example.demo.config; import com.example.demo.model.Result; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; @Slf4j @ResponseBody @ControllerAdvice public class ErrorHandler { @ExceptionHandler public Result exception(Exception e){ log.error("发生异常e{}",e); return Result.fail("内部错误"); } @ExceptionHandler public Result exception(NullPointerException e){ log.info("发生异常"); return Result.fail("NullPointerException异常,请联系管理员"); } @ExceptionHandler public Result exception(ArithmeticException e){ log.info("发生异常"); return Result.fail("ArithmeticException 异常,请联系管理员"); } }
Spring容器最开始启动时,会进行初始化的工作,其中会对异常进行处理,当异常项有多个匹配的时候,Spring会对其顺序依次排,找出最符合的报异常。
述源码可以看出 @ControllerAdvice 派⽣于 @Component 组件, 这也就是为什么没有五
⼤注解, ControllerAdvice 就⽣效的原因
当Controller抛出异常时, DispatcherServlet 通过
ExceptionHandlerExceptionResolver 来解析异常,⽽
ExceptionHandlerExceptionResolver ⼜通过 ExceptionHandlerMethodResolver 来解析异常, ExceptionHandlerMethodResolver 最终解析异常找到适⽤的@ExceptionHandler标注