✏️作者:银河罐头
系列专栏:JavaEE
“种一棵树最好的时间是十年前,其次是现在”
Spring 中提供了具体的实现拦截器:HandlerInterceptor,拦截器的实现分为以下两个步 骤:
public class LoginInterceptor implements HandlerInterceptor {
// 调用目标方法之前执行的方法
// 此方法会返回一个 boolean 类型的值,
// 返回 true 表示拦截器验证成功,继续走后续流程,执行目标方法;
// 返回 false 表示拦截器验证失败,后续流程和目标方法不用执行
@Override
public 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 {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public 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 {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;// true => 调用 beforeBodyWrite() 方法
}
//返回数据之前进行重写
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// 假定标准格式是 HashMap -> {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;
}
}
保证始终返回的都是标准的格式。
@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 {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.removeIf(converter->converter instanceof StringHttpMessageConverter);
}
}
2.在统一数据重写时,单独处理 String 类型,让其返回一个 String 类型而不是 HashMap.
//返回数据之前进行重写
@SneakyThrows
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// 假定标准格式是 HashMap -> {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;
}