✏️作者:银河罐头
📋系列专栏:JavaEE
🌲“种一棵树最好的时间是十年前,其次是现在”
目录
- ⽤户登录权限效验
- Spring Boot 拦截器
- 自定义拦截器
- 将自定义拦截器加入到系统配置
- 拦截器实现原理
- 统一异常处理
- 创建一个异常处理类
- 创建异常监测的类和处理的方法
- 统一数据返回格式
- StringHttpMessageConverter
Spring Boot 统⼀功能处理模块,也是 AOP 的实战环节。
⽤户登录权限效验
Spring Boot 拦截器
Spring 中提供了具体的实现拦截器:HandlerInterceptor,拦截器的实现分为以下两个步 骤:
- 创建⾃定义拦截器,实现 HandlerInterceptor 接⼝的 preHandle(执⾏具体⽅法之前的预处理)方 法。
- 将⾃定义拦截器加⼊ WebMvcConfigurer 的 addInterceptors ⽅法中。
自定义拦截器
public class LoginInterceptor implements HandlerInterceptor {// 调用目标方法之前执行的方法// 此方法会返回一个 boolean 类型的值,// 返回 true 表示拦截器验证成功,继续走后续流程,执行目标方法;// 返回 false 表示拦截器验证失败,后续流程和目标方法不用执行@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//用户登录校验HttpSession session = request.getSession(false);if(session != null && session.getAttribute("session_userinfo") != null){return true;}// response.setStatus(401);response.setContentType("application/json;charset=utf8");
// response.setCharacterEncoding("utf8");response.getWriter().println("{\"code\": -1, \"msg\": \"登录失败\", \"data\": \"\"}");return false;}
}
将自定义拦截器加入到系统配置
@Configuration
public class MyConfig implements WebMvcConfigurer {@Autowiredprivate LoginInterceptor loginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginInterceptor).addPathPatterns("/**")// 拦截所有 url.excludePathPatterns("/user/login") // 排除 登录 不拦截.excludePathPatterns("/user/reg") // 排除 注册 不拦截.excludePathPatterns("/image/**");}
}
验证下拦截功能:
@RestController
@RequestMapping("/user")
public class UserController {@RequestMapping("/login")public String login(){return "login";}@RequestMapping("/index")public String index(){return "index";}@RequestMapping("/reg")public String reg(){return "reg";}
}
login() 和 reg() 没有被拦截,index() 被拦截。
拦截器实现原理
正常情况下的调⽤顺序:
然⽽有了拦截器之后,会在调⽤ Controller 之前进⾏相应的业务处理,执⾏的流程如下图所示:
Spring Boot 拦截器的实现原理是基于 Spring MVC 框架的拦截器机制,当客户端发送请求时,请求会经过一系列的组件处理,其中就包括拦截器。
统一异常处理
如果不做统一的异常处理,后端抛异常,返回前端的状态码就是 500。
如果不想返回的是这个 500 状态码,可以对异常做统一处理,降低前端程序员和后端程序员的沟通成本。
创建一个异常处理类
@ControllerAdvice// 随着 spring Boot 项目的启动而启动 + 检测 controller 的异常
public class MyExceptionAdvice {}
创建异常监测的类和处理的方法
@ControllerAdvice
@ResponseBody
public class MyExceptionAdvice {@ExceptionHandler(NullPointerException.class)public HashMap<String, Object> doNullPointerException(NullPointerException e){HashMap<String, Object> result = new HashMap<>();result.put("code",-1);result.put("msg","空指针: " + e.getMessage());result.put("data",null);return result;}
}
@RestController
@RequestMapping("/user")
public class UserController {@RequestMapping("/login")public int login(){Object obj = null;System.out.println(obj.hashCode());return 1;}
}
但是这里只是处理了"空指针"异常,如果有其他的异常呢?比如"算数异常"
@RestController
@RequestMapping("/user")
public class UserController {@RequestMapping("/login")public int login(){int num = 10/0;return 1;}
}
有一个办法,是再去写一个处理"算数异常"的类,但是异常类型太多,这样很麻烦。
可以写一个类,处理所有异常的父类-“Exception”.
@ControllerAdvice
@ResponseBody
public class MyExceptionAdvice {@ExceptionHandler(Exception.class)public HashMap<String, Object> doException(Exception e){HashMap<String, Object> result = new HashMap<>();result.put("code",-1);result.put("msg","Exception: " + e.getMessage());result.put("data",null);return result;}
}
- 如果子类异常(空指针)和父类异常都存在的情况下,出现"空指针"的情况会触发子类还是父类异常处理?
@ControllerAdvice
@ResponseBody
public class MyExceptionAdvice {@ExceptionHandler(NullPointerException.class)public HashMap<String, Object> doNullPointerException(NullPointerException e){HashMap<String, Object> result = new HashMap<>();result.put("code",-1);result.put("msg","空指针: " + e.getMessage());result.put("data",null);return result;}@ExceptionHandler(Exception.class)public HashMap<String, Object> doException(Exception e){HashMap<String, Object> result = new HashMap<>();result.put("code",-1);result.put("msg","Exception: " + e.getMessage());result.put("data",null);return result;}
}
@RestController
@RequestMapping("/user")
public class UserController {@RequestMapping("/login")public int login(){Object obj = null;System.out.println(obj.hashCode());return 1;}@RequestMapping("/login2")public int login2(){int num = 10/0;return 1;}
}
优先触发子类异常。
统一数据返回格式
强制性统一数据返回。(在返回数据之前进行数据重写)
统⼀数据返回格式的优点:⽅便前端程序员更好的接收和解析后端数据接⼝返回的数据。 降低前端程序员和后端程序员的沟通成本,按照某个格式实现就⾏了,因为所有接⼝都是这样返回 的。有利于后端技术部⻔的统⼀规范的标准制定,不会出现稀奇古怪的返回内容。
//统一数据格式处理
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;// true => 调用 beforeBodyWrite() 方法}//返回数据之前进行重写@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {// 假定标准格式是 HashMap<String, Object> -> {code, msg, data}if(body instanceof HashMap) {return body;}HashMap<String, Object> result = new HashMap<>();result.put("code", 200);result.put("msg","");result.put("data",body);return result;}
}
@RestController
@RequestMapping("/user")
public class UserController {@RequestMapping("/login")public int login(){return 1;}@RequestMapping("/login2")public int login2(){return 1;}@RequestMapping("/reg")public HashMap<String, Object> reg(){HashMap<String, Object> result = new HashMap<>();result.put("code",200);result.put("msg","");result.put("data",1);return result;}
}
保证始终返回的都是标准的格式。
StringHttpMessageConverter
- 再来看一个例子:
@RequestMapping("/sayHi")
public String sayHi(){return "say hi";
}
//默认异常处理(当具体的异常匹配不到时,会走这个方法)
@ExceptionHandler(Exception.class)
public HashMap<String, Object> doException(Exception e){HashMap<String, Object> result = new HashMap<>();result.put("code",-1);result.put("msg","Exception: " + e.getMessage());result.put("data",null);return result;
}
返回流程:
1.返回 String
2.统一数据格式处理:String -> HashMap
3.HashMap -> application/json 给前端
报错就是 第 3 步出错了。
到 第 3 步之后就会对原 body 的类型进行判断:
1.是 String -> StringHttpMessageConverter 进行类型转换
就这个例子而言,它就会用第 2 步得到的 HashMap -> String, 就出现类型转换异常。
2.非 String -> HttpMessageConverter 进行类型转换
解决方案:
1.将 StringHttpMessageConverter 转化器去掉。
package com.example.demo.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;@Configuration
public class MyConfig implements WebMvcConfigurer {@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {converters.removeIf(converter->converter instanceof StringHttpMessageConverter);}
}
2.在统一数据重写时,单独处理 String 类型,让其返回一个 String 类型而不是 HashMap.
//返回数据之前进行重写@SneakyThrows@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {// 假定标准格式是 HashMap<String, Object> -> {code, msg, data}if(body instanceof HashMap) {return body;}HashMap<String, Object> result = new HashMap<>();result.put("code", 200);result.put("msg","");result.put("data",body);if(body instanceof String){
// return "{\"code\": 200, \"msg\": \"\", \"data\": \"" + body + "\"}";//将对象转换成 json 字符串return objectMapper.writeValueAsString(result);}return result;}