Spring Boot 统一功能处理

✏️作者:银河罐头
系列专栏:JavaEE

“种一棵树最好的时间是十年前,其次是现在”

目录

  • ⽤户登录权限效验
    • Spring Boot 拦截器
      • 自定义拦截器
      • 将自定义拦截器加入到系统配置
    • 拦截器实现原理
  • 统一异常处理
    • 创建一个异常处理类
    • 创建异常监测的类和处理的方法
  • 统一数据返回格式
    • StringHttpMessageConverter

Spring Boot 统⼀功能处理模块,也是 AOP 的实战环节。

⽤户登录权限效验

Spring Boot 拦截器

Spring 中提供了具体的实现拦截器:HandlerInterceptor,拦截器的实现分为以下两个步 骤:

  1. 创建⾃定义拦截器,实现 HandlerInterceptor 接⼝的 preHandle(执⾏具体⽅法之前的预处理)方 法。
  2. 将⾃定义拦截器加⼊ WebMvcConfigurer 的 addInterceptors ⽅法中。

自定义拦截器

Spring Boot 统一功能处理_第1张图片

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";
    }
}

image-20230604194230354

Spring Boot 统一功能处理_第2张图片

Spring Boot 统一功能处理_第3张图片

login() 和 reg() 没有被拦截,index() 被拦截。

拦截器实现原理

正常情况下的调⽤顺序:

Spring Boot 统一功能处理_第4张图片

然⽽有了拦截器之后,会在调⽤ Controller 之前进⾏相应的业务处理,执⾏的流程如下图所示:

Spring Boot 统一功能处理_第5张图片

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;
    }
}

Spring Boot 统一功能处理_第6张图片

但是这里只是处理了"空指针"异常,如果有其他的异常呢?比如"算数异常"

@RestController
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/login")
    public int login(){
        int num = 10/0;
        return 1;
    }
}

Spring Boot 统一功能处理_第7张图片

有一个办法,是再去写一个处理"算数异常"的类,但是异常类型太多,这样很麻烦。

可以写一个类,处理所有异常的父类-“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;
    }
}

Spring Boot 统一功能处理_第8张图片

  • 如果子类异常(空指针)和父类异常都存在的情况下,出现"空指针"的情况会触发子类还是父类异常处理?
@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;
    }
}

Spring Boot 统一功能处理_第9张图片

优先触发子类异常。

统一数据返回格式

强制性统一数据返回。(在返回数据之前进行数据重写)

统⼀数据返回格式的优点:⽅便前端程序员更好的接收和解析后端数据接⼝返回的数据。 降低前端程序员和后端程序员的沟通成本,按照某个格式实现就⾏了,因为所有接⼝都是这样返回 的。有利于后端技术部⻔的统⼀规范的标准制定,不会出现稀奇古怪的返回内容。

//统一数据格式处理
@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;
    }
}

Spring Boot 统一功能处理_第10张图片

Spring Boot 统一功能处理_第11张图片

保证始终返回的都是标准的格式。

StringHttpMessageConverter

  • 再来看一个例子:
@RequestMapping("/sayHi")
public String sayHi(){
    return "say hi";
}

Spring Boot 统一功能处理_第12张图片

//默认异常处理(当具体的异常匹配不到时,会走这个方法)
@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 步出错了。

Spring Boot 统一功能处理_第13张图片

到 第 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;
    }

Spring Boot 统一功能处理_第14张图片

你可能感兴趣的:(JavaEE进阶,spring,boot,java,spring)