统一功能处理
SpringBoot统一功能处理
本节目表
1.掌握拦截器的使用,及其原理
2.学习统一数据返回格式和统一异常处理的操作
3.了解一些Spirng的源码
文章目录
- SpringBoot统一功能处理
- 前言
- 一、拦截器是什么?
- 二、使用步骤
- 1.定义拦截器
- 2.注册配置拦截器
- 三,适配器
- 四丶统一数据返回格式
- 五、统一异常处理
- 总结
前言
提示:这里可以添加本文要记录的大概内容:
前面我们实现了强制登录的功能,后端程序根据Session来判断用户是否登录,但是实现方法是比较麻烦的
需要修改每个接口的处理逻辑
需要修改每个接口的返回结果
接口定义修改,前段代码也需要跟着修改
提示:以下是本篇文章正文内容,下面案例可供参考
一、拦截器是什么?
拦截器是Spring框架提供的核心功能之一,主要用来拦截用户的请求,在指定方法(接口)前后,根据业务需要执行预先设定的代码
也就是说,允许开发人员提前预定以一些逻辑,在用户请求响应前后执行,也可以在用户请求前阻止其执行
在拦截器当中,开发人员可以在应用程序中做一些通用性的操作,比如通过拦截器来拦截前端发来的请求,判断Session中是否有登录用户信息,如果有就可以放心,如果没有就进行拦截
生活中实际的例子: 比如我们去银行办理业务,在办理业务前后,就可以加一些拦截操作 办理业务之前,先取号,如果带了身份证了就取号成功
业务办理结束,给业务办理人员进行评价 这就是"拦截器"做到工作
二、使用步骤
拦截器使用步骤有两步
- 定义拦截器 2. 注册拦截器
1.定义拦截器
自定义拦截器:实现HandlerInterceptor接口, 并重写其所有方法
代码如下(示例):登录拦截器
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
//定义拦截器 实现父类的所有方法
@Autowired
private ObjectMapper objectMapper;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("登录拦截...");
//true 放行 false 拦截
//判断用户是否登录 判空和有值
HttpSession session = request.getSession();
if(session.getAttribute(Constants.SESSION_USER_KEY)==null||
!StringUtils.hasLength((String) session.getAttribute(Constants.SESSION_USER_KEY))){
//未登录进行拦截 ,需要设置一些响应 返回给客服端
log.error("用户未登录,进行拦截");
response.setStatus(HttpStatus.UNAUTHORIZED.value());//设置状态码,没权限401
response.setContentType("application/json,charset=utf-8");//设置返回数据格式为json
// response.getOutputStream().write("{\"ok\" : false}".getBytes());//设置返回的内容
Result result = Result.unlogin();//登录失败,返回统一格式
response.getOutputStream().write(objectMapper.writeValueAsString(result).getBytes());
return false;
}
log.info("用户登录校验通过");
return true;
}
代码说明
创建了一个拦截器LoginInterceptor类 ,实现了HandlerInterceptor接口,使用接口中的preHandle()方法,返回值为bool类型 true 为放行,false 为拦截 ,当前拦截器是判断是否存在登录的Session,有就登录成功,执行后续代码,否则可以设置一些返回结果比如设置状态码,设置返回数据格式,统一返回响应内容
> 记得在前后打印日志 !!!
2.注册配置拦截器
代码如下(示例):
@Slf4j
@Configuration
public class WebConfig implements WebMvcConfigurer {
//注册web拦截器,实现拦截器功能和指定接口
//注入“登录拦截器”
@Autowired
private LoginInterceptor loginInterceptor;
//实现父类方法(注册拦截器)
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/book/**");
}
}
代码说明
创建了WebConfig类 实现了 WebMvcConfigurer的接口 见名思意,“webmvc” 就要设计到一些前后端交互的内容,那么这个类真正的作用在于,注册拦截器,设置拦截器的使用目标,
代码体现在于,实现了父类的方法 addInterceptors (添加拦截器) ,根据参数registry来调用 addInterceptor方法进行添加我们定义的LoginInterceptor, addPathPatterns(“/**”)表示前后端交互的接口,对那些接口使用拦截器当然 也可以对这些接口中的特殊进行限制不使用拦截器 即excludePathPatterns方法
当用户访问一个接口时,第一时间并不是直接去和Controller进行交互,而是先通过“保安” (拦截器)验证身份之后才能顺利与Controller获取联系
拦截路径说明
拦截流程
正常的调用顺序:
有了拦截器之后,会在调用Controller之前进行相应的业务处理,执行的流程图如下
1.添加拦截器后,执行Controller方法之前,请求首先会被拦截器拦截住,执行preHandle()方法,这个方法需要返回一个布尔类型值,如果返回true,就表示放行本次操作,继续访问Controller中的方法,如果返货false,则不会放行
2.controller当中的方法执行完毕后,再回来执行PostHandle()这个方法以及afterCompletion()方法,执行完毕后,最终响应结果给浏览器
三,适配器
适配器模式定义:适配器模式,也叫包装器模式,将一个类的接口转换成客户期望的另一个接口,适配器让原本接口不兼容的类可以合作无间。
简单来说就是目标类不能直接使用,通过一个新类进行包装一下,适配器调用方使用。把两个不兼容的接口通过一定的方式使之兼容
比如下面两个接口,本身是不相容的(参数类型,个数都不一样)
通过适配器的方式,使之兼容
实际生活中的列子, 转换插头!!
对于java选手来说,面向对象变成 ,我们就得到实际摸得着的类入手
因此适配器模式角色可以分为以下几个对象
- Target:目标接口,客服希望直接使用的接口
- Adaptee:适配者,与Target不兼容
- Adaptor:适配器类,此模式的核心,通过继承或者引用适配者的对象,把适配转为目标接口
- client:需要使用适配器的对象
适配器模式的实现
场景:前面学习的skf4j就使用了适配器模式,slf4j提供了一系列打印日志的api,底层调用的是log4j或者logback来打印日志,我们作为调用者,止血药调用slf4j的api就行了
Slf4jApi为目标接口,Log4jApi用户接口 ,需要一个 Slf4jLog4jAdaptor适配器使两者兼容,Client是用户使用的类
目标接口
public interface Slf4jApi {
//目标接口
public void log(String log);
}
用户需要的接口
public class Log4jApi {
public String print(String log) {
return log;
}
}
适配器
public class Slf4jLog4jAdaptor implements Slf4jApi {
private Log4jApi log4jApi;
public Slf4jLog4jAdaptor(Log4jApi log4jApi) {
this.log4jApi = log4jApi;
}
@Override
public void log(String log) {
log4jApi.print(log);
}
}
客服端调用
public class Client {
public static void main(String[] args) {
Slf4jApi slf4j = new Slf4jLog4jAdaptor(new Log4jApi());
slf4j.log("打印日志");
}
}
可以看出,我们不需要改变log4j的api(提前规定好的接口 ,不能修改),只能使用适配进行转换,就可以更换日志框架,保障系统的运行
应用场景
一般来说,适配器模式可以看作一种“补偿模式”,用来补救设计上的缺陷,应用这种模式算是无奈之举,如果在设计初期,我们就能协调规避接口不兼容的问题,就不需要使用适配器模式了
所以适配器模式更多的应用场景主要是对正在运行的代码进行改造,并且希望可以复用原来代码的实现功能
四丶统一数据返回格式
在强制登录的案例中,我们做了两部分工作
1.通过Session来判断用户是否登录
2.对后端返回的数据进行封装,告诉前段处理的结果
对于第一个问题,拦截器可以实现对需要的接口进行了拦截的处理,以此判断用户是否登录,这一实现避免了之前对每个接口都要进行校验Session是否有值,而使用拦截器统一的处理会更方便,简单!!!
对于第二个问题,在之前我们需要对每一个接口进行封装,响应给前端的统一结果也有进行修改,因此,SpringBoot 提供了统一返回结果的功能
对接口进行封装
封装对象
@Data
public class Result<T> {
private Integer code;
private String errMsg;
private T data;
public static <T> Result<T> success(T data) {
Result result = new Result();
result.setCode(Constants.SUCCESS_CODE);
result.setErrMsg("");
result.setData(data);
return result;
}
public static <T> Result<T> unlogin() {
Result result = new Result();
result.setCode(Constants.UNLOGIN_CODE);
result.setErrMsg("用户未登录");
return result;
}
public static <T> Result<T> fail() {
Result result = new Result();
result.setCode(Constants.FAIL_CODE);
result.setErrMsg("程序出错");
return result;
}
}
代码说明
统一返回Result对象
属性:
1.code 表示状态码 200 登陆成功 -1:未登录 -2:程序出错 通过 Constant静态常量 来自定义;
2.errMSg 表示错误信息 success(“”) ,fail(“程序出错”), unlogin(“未登录”);
3.data 表示各类接口返回的信息, 返回类型不同 , 使用泛型
方法: public static Result xxx(){}
1.success(T data) 表示登录成功, 创建并设置成功时的信息(code,errmsg,data),并返回这个对象
2.fail() 表示登录失败,程序出错,创建并设置失败的信息,返回该对象
3.unlogin 表示未登 , 创建并设置未登录的信息,返回该对象
现在,我们使用了统一结果返回的功能后,简化了对每个接口的封装效果,有效的整理成了如下代码
以下是代码演示:使目标接口返回结果统一是Result类型
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
@Autowired
private ObjectMapper 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) {
if (body instanceof Result<?>){
return body;
}
if (body instanceof String){
return objectMapper.writeValueAsString(Result.success(body));
}
return Result.success(body);
}
}
代码说明
统一数据返回格式使用@ControllerAdvice 和 ResponseBodyAdvice的方式实现@ControAdvice表示控制器通知类,
**添加类ResponseAdvie,实现ResponseBodyAdvice接口,并在接口添加@ControllerAdvice注解
方法说明
1.supports方法,通过returnType参数来获取某个类某个方法
判断是否要执行beforeBodyWrite方法,true表示执行,false不执行,通过该方法可以选择那些类或那些方法的response要进行处理,其他的不进行处理,(上述代码直接设定了true表示对每个接口都要进行处理)
2.beforeBodyWrite方法,通过body来接收每个接口返回的响应response
如果一些方法结果已经是Result类型,直接返回body,不做处理
特例是若body是String类型,需要额外的进行处理(目前不懂这串代码):(具体含义是把Result对象转换为了String字符串,而不是对象,因此需要转换为JSON对象)
其他情况下,一律返回Result的类型
若需要测试,统一使用postman进行测试
优点:
1.方便前端程序员更好的接收和解析后端数据接口返回的数据
2.降低前端程序员和后端程序员的沟通成本,按照某个格式实现就可以了,因为所有接口都是这样返回的
3.有利于项目统一数据的维护和修改
4.有利于后端技术部门的统一规范的标准规定,不会出现稀奇古怪的返回内容
五、统一异常处理
测试异常
@RestController
@RequestMapping("/test")
public class TestController {
@RequestMapping("/t1")
public String t1(){
int a = 10/0;
return "t1";
}
//数组越界异常
@RequestMapping("/t2")
public int t2(){
int[] arr = new int[3];
return arr[4];
}
//空指针异常
@RequestMapping("/t3")
public int t3(){
String a = null;
return a.length();
}
}
捕获异常
@Slf4j
@ControllerAdvice
@ResponseBody
public class ExceptionAdvice {
//捕获程序中出现的异常,进行处理
@ExceptionHandler
public Object hander(Exception e){
log.error("发生异常 e:{}" , e);
return Result.fail();
}
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler
public Object handler(ArrayIndexOutOfBoundsException e){
log.error("发生异常 e:{}" , e);
return Result.fail("发生数组越界异常...");
}
@ExceptionHandler
public Object handler(NullPointerException e){
log.error("发生异常 e:{}" , e);
return Result.fail("发生空指针异常...");
}
}
代码说明
建立ExceptionAdvice类, 使用了类@ControllerAdvice (表示控制器通知类)和方法注解@ExceptionHandler(表示异常处理器)进行捕获异常,两者结合表示当出现异常的时候执行某个通知,也就是执行某个方法事件
如果程序中发生了异常,就会被改类捕捉到并且匹配到响应的异常,若匹配不到就直接可以匹配Exception的子类
注意
接口返回数据时,需要加上@ResponseBody注解 或则会返回视图导致找不到而报错
!!!!
**@ResponseStatus()注解是用来设置状态码!!!**
总结
提示:这里对文章进行总结:
本节课我们实现了拦截器,统一返回数据格式,统一异常处理,实现了对每个接口进行了 “封装” ,在访问这个接口前执行了什么方法逻辑,或是在返回后的数据执行了什么方法逻辑, 又或者是在接口中出现问题的时候进行了统一的处理。这极大的抽取了共同的需求,和简化了真实的封装效果,为项目进一步的改善。