前言👀~
上一章我们介绍了Spring IoC&DI,今天介绍SpringBoot对于统一功能的处理,这期的代码在我的gitee上,链接在这可以拿去试试Nan/Book - 码云 - 开源中国 (gitee.com)
如果各位对文章的内容感兴趣的话,请点点小赞,关注一手不迷路,讲解的内容我会搭配我的理解用我自己的话去解释如果有什么问题的话,欢迎各位评论纠正 🤞🤞🤞
个人主页:N_0050-CSDN博客
相关专栏:java SE_N_0050的博客-CSDN博客 java数据结构_N_0050的博客-CSDN博客 java EE_N_0050的博客-CSDN博客
什么是拦截器?
拦截器是Spring框架提供的核⼼功能之⼀, 主要⽤来拦截用户的请求, 在指定⽅法前后, 根据业务需要执⾏预先设定的代码。例如公司的保安拦截不是工作人员的人员如果不是工作人员需要登记后才可放行,也可以想象成打给银行电话办理一些业务的时候,需要先输入手机号啊银行卡啊密码啊选择业务啊校验你的身份,成功才会转到人工服务。它的作用维度是URL
拦截器的基本使⽤:
• 拦截器的使⽤步骤分为两步:
• **1. 定义拦截器**,实现HandlerInterceptor接口,重写里面的preHandle和postHandle方法
• **2. 注册配置拦截器**,就是把拦截器注册到项目中,也就是使用拦截器。创建一个配置类然后实现WebMvcConfigurer接口重写里面的addInterceptors方法
小结:就是先自定义一个拦截器在方法里写好代码,然后把这个拦截器实现到项目中,例如每个方法执行前先过一遍拦截器的方法。如果没设置拦截路径,拦截器就是在每个方法执行前会先执行一下拦截器里的逻辑
演示如下,下面我访问一个URL在未登录的情况下会强制跳转到登录界面
@Slf4j
//交给Spring进行管理
@Component
public class LoginInterceptor implements HandlerInterceptor {
//目标方法执行前执行 返回true 表示放行继续后续操作 false表示拦截成功
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("登录拦截校验...");
HttpSession session = request.getSession(false);//默认true会创建一个session false不会创建
if (session != null && session.getAttribute(Constants.SESSION_USER_KEY) != null) {
return true;
}
response.setStatus(401);//表示未登录
return false;
}
//目标方法执行后执行 类似业务办理后给工作人员一个评价
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("目标方法执行后");
}
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
private static List<String> excludePath = Arrays.asList("/user/login", "/css/**", "/js/**", "/pic/**", "/**/*.html", "/test/t1", "/test/t2");
@Override
public void addInterceptors(InterceptorRegistry registry) {
//添加拦截器 拦截路径相当于对大领导直接放行
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns(excludePath);// /**表示给所有方法添加拦截器
}
}
它会直接跳转到登录的URL
拦截路径
拦截路径是指我们定义的这个拦截器, 对哪些请求生效,可以简单理解为接口或方法。拦截路径相当于保安对非工作人员进行拦截,对大领导直接放行。注册拦截器的时候,通过 addPathPatterns() ⽅法指定要拦截哪些请求,也可以通过excludePathPatterns() 指定不拦截哪些请求
代码如下:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
private static List<String> excludePath = Arrays.asList("/user/login", "/css/**", "/js/**", "/pic/**", "/**/*.html", "/test/t1", "/test/t2");
@Override
public void addInterceptors(InterceptorRegistry registry) {
//添加拦截器 拦截路径相当于对大领导直接放行
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns(excludePath);// /**表示给所有方法添加拦截器
}
}
拦截器执行流程
• **正常的调用顺序**
• **有了拦截器之后,会在调用 Controller 之前进行相应的业务处理,执行的流程**
1. 添加拦截器后, 执行Controller的⽅法之前,请求会先被拦截器拦截住,执行 preHandle() ⽅法,这个⽅法需要返回⼀个布尔类型的值。如果返回true, 就表示放行本次操作, 继续访问controller中的⽅法. 如果返回false,则不会放行(controller中的⽅法也不会执行)。简单点说就是加一个拦截器然后会在你指定的拦截路径对进行拦截,先执行拦截器的方法,如果true就执行controller中的方法,否则不执行。相当于你上厕所前先脱裤子,裤子不脱怎么上
2. controller当中的⽅法执⾏完毕后,再回过来执行 postHandle() 这个方法以及afterCompletion() ⽅法,执行完毕之后,最终给浏览器响应数据。相当于上完厕所后再冲水
小结:大白话解释就是第一步相当于是给保安分配任务对于除工作人员以外的人进行拦截,第二步相当于给保安安排在哪个位置进行拦截
适配器模式
• HandlerAdapter 在 Spring MVC 中使⽤了适配器模式
适配器模式也叫包装器模式,将⼀个类的接口,转换成客户期望的另⼀个接口,适配器让原本接口不兼容的类可以合作。简单来说就是⽬标类不能直接使⽤,通过⼀个新类进⾏包装⼀下, 适配调用方使⽤,把两个不兼容的接口通过⼀定的⽅式使之兼容
例子:例如我们要投诉某个公司我们可以打某某电话然后通过这个电话再举报到某某局,例如工商局、派出所,如果没有这个电话我们不能直接进行举报,可以这样理解适配器,生活中使用的转接头也是一个例子,所以它是适配两者之间的差异
适配器模式角色:
Target: ⽬标接⼝ (可以是抽象类或接口),客户希望直接⽤的接口。如上图中的B
Adaptee: 适配者,但是与Target不兼容。如上图中的A
Adapter: 适配器类,此模式的核⼼,通过继承或者引⽤适配者的对象, 把适配者转为⽬标接口。如上图中的适配器
client: 需要使用适配器的对象
适配器模式的实现:
下面进行一个简单的模拟
public interface Slf4jApi {
void print(String message);
}
public class Log4j {
public void print(String message) {
System.out.println("我是Log4j:" + message);
}
}
public class Slf4jLog4jAdapter implements Slf4jApi {
private Log4j log4j;
public Slf4jLog4jAdapter(Log4j log4j) {
this.log4j = log4j;
}
@Override
public void print(String message) {
log4j.print(message);
}
}
public class Main {
public static void main(String[] args) {
Slf4jLog4jAdapter slf4jLog4jAdapter = new Slf4jLog4jAdapter(new Log4j());
slf4jLog4jAdapter.print("使⽤slf4j打印⽇志");
}
}
输出结果,会发现我们使用slf4j打印日志用的是log4j日志框架,这个Slf4jLog4jAdapter 就是适配器的核心,也是适配器模式的体现
适配器模式应用场景:****适配器模式可以看作⼀种"补偿模式",用来补救设计上的缺陷。适配器模式可以看作⼀种"补偿模式",用来补救设计上的缺陷。如果在设计初期,我们就能协调规避接⼝不兼容的问题, 就不需要使⽤适配器模式了。适配器模式更多的应⽤场景主要是对正在运行的代码进⾏改造, 并且希望可以复⽤原有代码实现新的功能,⽐如版本升级等。简单点说一般在写代码初期如果能避免出现接口不兼容的情况也就怎么需要用到适配器模式,简单说这个模式是弥补这个设计上的缺陷
统⼀数据返回格式
对数据返回格式的统一。自定义一个类实现ResponseBodyAdvice接口,搭配@ControllerAdvice注解,重写接口中的方法即可
代码如下,下面进行演示,我们发送一个请求看看返回数据的格式
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
//判断是否要执⾏beforeBodyWrite⽅法. true为执⾏, false不执⾏
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
//对response⽅法进⾏具体操作处理 就是对方法返回结果之前 需要做的事情
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
return Result.success(body);
}
}
输出结果
• **上面图中会发现题如果返回类型本身就是Result的话,会再进行一层的封装**,**原代码如下**
@RequestMapping("/getBookByPage")
public Result getBookByPage(PageRequest pageRequest, HttpSession session) {
log.info("接收到分页请求,pageRequest:{}", pageRequest);
//请求参数验证
if (pageRequest.getCurrentPage() < 1 || pageRequest.getPageSize() < 0) {
return Result.fail("参数返回失败");
}
PageResult<BookInfo> BookResult = null;
try {
//响应成功返回数据
BookResult = bookService.selectBookByPage(pageRequest);
return Result.success(BookResult);
} catch (Exception e) {
log.error("查询翻页信息出错,e:{}", e);
//响应失败
return Result.fail(e.getMessage());
}
}
• **还有一个问题如果返回类型是String类型,不能正常进行处理,其他类型都可以。****原代码如下**
@RequestMapping(value = "/updateBook", produces = "application/json")
public String updateBook(BookInfo bookInfo) {
log.info("接收到更新图书信息请求,bookInfo:{}", bookInfo);
Integer result = bookService.updateBook(bookInfo);
if (result == 0) {
log.error("更新出错,bookInfo:{}", bookInfo);
return "更新出错,请联系管理员";
}
return "";
}
• **正确处理**
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
@Autowired
private ObjectMapper objectMapper;
//判断是否要执⾏beforeBodyWrite⽅法. true为执⾏, false不执⾏
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@SneakyThrows
//对response⽅法进⾏具体操作处理 就是对方法返回结果之前 需要做的事情
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
//把返回的结果封装到这个方法中再返回 body就是返回的结果
if (body instanceof Result) {
return body;
}
//如果返回结果为String类型, 使⽤SpringBoot内置提供的Jackson来实现信息的序列化
if (body instanceof String) {
return objectMapper.writeValueAsString(Result.success(body));
}
return Result.success(body);
}
}
输出结果,此时输出符合预期了
统一数据返回格式优点:
• 1.⽅便前端程序员更好的接收和解析后端数据接口返回的数据
• 2.降低前端程序员和后端程序员的沟通成本, 按照某个格式实现就可以了
• 3.有利于项⽬统⼀数据的维护和修改
• 4.有利于后端技术部⻔的统⼀规范的标准制定
统一异常处理
• 如果我们的代码中出现异常,依然执行的是统一返回数据格式类的方法,这显然不合理
统⼀异常处理使⽤的是 @ControllerAdvice + @ExceptionHandler 来实现的,@ControllerAdvice 表示控制器通知类, @ExceptionHandler 是异常处理器加在方法上,两个结合表示当出现异常的时候执行某个通知,也就是执行某个⽅法事件
注意:接口返回为数据时, 需要加 @ResponseBody 注解。如果不加的话又有拦截器的话,返回的数据会被当作是一个页面,然后找页面的时候要走http请求,走http请求就需要走拦截器,拦截器生效会拦下
我们可以针对不同的异常, 返回不同的结果,统一异常处理,当有多个异常通知时,匹配顺序又是怎么样的呢?
** • 例子演示**
@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.error("发生异常,e:{}", e);
return Result.fail("NullPointerException异常,请联系管理员");
}
@ExceptionHandler
public Result exception(ArithmeticException e) {
log.error("发生异常,e:{}", e);
return Result.fail("ArithmeticException异常,请联系管理员");
}
}
@RequestMapping("/test")
@RestController
public class TestController {
@RequestMapping("/t1")
public Boolean t1() {
int a = 10 / 0;
return true;
}
@RequestMapping("/t2")
public Integer t2() {
String a = null;
return a.length();
}
}
先尝试触发一个算术异常看看效果,输出结果如下
再试试触发一个空指针看看效果,输出效果如下
小结:当有多个异常通知时,匹配顺序为当前类及其⼦类向上依次匹配!!!
小结:Spring对于异常的处理,Spring容器在最开始启动时,会先进行一些初始化工作,其中有一个就是异常模块的处理,我们在定义异常处理类,如果异常有多个适配的时候,Spring ExcptionHandler会对这几个进行排序,由小到大
为什么没有五大注解, ControllerAdvice 就能生效呢?
源码可以看出 @ControllerAdvice 派生于 @Component 组件,这也就是为什么没有五大注解, ControllerAdvice 就⽣效的原因
以上便是本章SpringBoot统一功能处理的内容,其实还有些内容但是整理有点麻烦,我就没整理到这了😁后面看情况补充上,我们下一章再见💕
版权归原作者 N_0050 所有, 如有侵权,请联系我们删除。