SpringBoot 统一功能处理

简介: SpringBoot 统一功能处理

一、用户登录拦截器

1、拦截器实现步骤

步骤1:自定义拦截器

// 自定义拦截器
@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(
            HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 业务逻辑
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute(AppVar.SESSION_KEY) != null) {
            // 返回 true -> 拦截器验证成功,继续执行后续的方法
            return true;
        }
        // 返回 false -> 拦截器验证失败,不会执行后续的目标方法
        return false;
    }
}

代码解析:

  1. 通过 @Component 注解将 LoginInterceptor 类标记为一个 Spring 组件,使其成为 Spring 容器中的一个可被管理的 Bean。
  2. LoginInterceptor 类实现了 Spring 提供的拦截器接口 HandlerInterceptor,并覆盖了其中的 preHandle 方法。preHandle 方法在目标方法执行前被调用。
  3. 在 preHandle 方法中,首先通过 HttpServletRequest 获取当前请求的 HttpSession 对象。如果 HttpSession 不为 null,且其中存储的 AppVar.SESSION_KEY 属性不为 null,表示用户已登录。
  4. 如果验证成功,即用户已登录,返回 true,表示拦截器验证通过,可以继续执行后续的目标方法;如果验证失败,即用户未登录,返回 false,表示拦截器验证失败,不会执行后续的目标方法。

步骤2:将自定义拦截器配置到系统设置中,并设置拦截规则

@Configuration
public class AppConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;
    // 在系统配置中添加拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/login.html")
                .excludePathPatterns("/reg.html")
                .excludePathPatterns("/css/**")
                .excludePathPatterns("/editor.md/**")
                .excludePathPatterns("/img/**")
                .excludePathPatterns("/js/**");
    }
}

代码解析:

  1. 通过 @Autowired 注解将 LoginInterceptor 注入到 AppConfig 类中,该拦截器在上面已经定义好了。
  2. 在 addInterceptors 方法中,通过 registry.addInterceptor(loginInterceptor) 将 LoginInterceptor 拦截器添加到拦截器链中。
  3. 使用 addPathPatterns 方法设置需要拦截的 URL,这里使用 “/**” 表示拦截所有的请求。
  4. 使用 excludePathPatterns 方法设置不需要拦截的 URL,这些路由在拦截器中会被忽略。这里排除了一些静态资源和特定路径,比如登录页、注册页、CSS 文件、图片文件和 JavaScript文件。

2、拦截器实现原理

本质上 Spring 中的拦截器也是通过动态代理和环绕通知的 思想 来实现的。在拦截器中,可以通过实现 HandlerInterceptor 接口并重写 preHandlepostHandleafterCompletion 方法来实现环绕通知的功能。


通过阅读源码我们可以看到,在 Spring 中所有 Controller 的执行都会通过一个核心调度器DispatcherServlet 来实现,所有的请求方法都会执行 DispatcherServlet 中的 doDispatch 调度方法,doDispatch 方法中有一系列的事件处理方法,而在开始执行 Controller 中的目标方法 之前,会先调用预处理方法 applyPreHandle,在 applyPreHandle 方法中会获取所有拦截器HandlerInterceptor 并执行拦截器中的 preHandle 方法。如果拦截器中有一个返回了 false 那么后续的流程就不会执行了。


二、统一异常处理


通过使用 @RestControllerAdvice(@ControllerAdvice+@ResponseBody) 注解和@ExceptionHandler 注解结合使用,可以实现全局的或是针对特定异常的统一异常处理,并将处理结果以统一的数据格式返回给客户端。

@RestControllerAdvice
public class ExceptionAdvice {
    // 仅限于空指针异常的异常处理
    @ExceptionHandler(NullPointerException.class)
    public ResultAjax doNullPointException(NullPointerException e) {
        ResultAjax resultAjax = new ResultAjax();
        resultAjax.setCode(-1);
        resultAjax.setMsg("异常"+e.getMessage());
        return resultAjax;
    }
    
    // 适用于所有异常的异常处理
    @ExceptionHandler(Exception.class)
    public ResultAjax doException(Exception e) {
        ResultAjax resultAjax = new ResultAjax();
        resultAjax.setCode(-1);
        resultAjax.setMsg("异常"+e.getMessage());
        return resultAjax;
    }
}

代码解析:

  1. @RestControllerAdvice 注解表示该类是一个全局控制器增强器,并且结合了 @ControllerAdvice 和 @ResponseBody 注解的功能。
  2. @ExceptionHandler 注解标注异常处理的方法。搭配 @RestControllerAdvice 注解可以在发生异常时统一处理异常并返回数据。
  3. doNullPointException 方法使用 @ExceptionHandler(NullPointerException.class) 注解来指定它处理的异常类型为NullPointerException。当发生空指针异常时,该方法会被调用。在方法体内,创建一个 ResultAjax对象并设置相应的错误信息,然后将其返回。
  4. doException 方法没有指定特定的异常类型,因此它将会处理所有类型的异常。当发生任何异常时,该方法会被调用。它的处理逻辑与 doNullPointException 方法类似,也是创建一个 ResultAjax 对象并设置错误信息,然后返回。

三、统一数据返回格式

统一数据的返回格式可以降低前端程序员和后端程序员的沟通成本,方便前端程序员更好的接收和解析后端数据接口返回的数据。


一般情况下,我们可以创建一个统一返回对象,提供一些成功和失败的返回接口,后续返回的数据直接调用接口即可返回约定的统一对象。具体实现如下:

// 定义统一返回对象
@Data
public class ResultAjax {
    // 状态码
    private int code;
    // 状态码的描述信息
    private String msg;
    // 返回数据
    private Object data;

    // 返回成功对象
    public static ResultAjax succ(Object data) {
        ResultAjax resultAjax = new ResultAjax();
        resultAjax.setCode(200);
        resultAjax.setMsg("");
        resultAjax.setData(data);
        return resultAjax;
    }
    public static ResultAjax succ(String msg, Object data) {
        ResultAjax resultAjax = new ResultAjax();
        resultAjax.setCode(200);
        resultAjax.setMsg(msg);
        resultAjax.setData(data);
        return resultAjax;
    }
    // 返回失败对象
    public static ResultAjax fail(int code,String msg){
        ResultAjax resultAjax = new ResultAjax();
        resultAjax.setCode(code);
        resultAjax.setMsg(msg);
        resultAjax.setData(null);
        return resultAjax;
    }

    public static ResultAjax fail(int code,String msg,Object data){
        ResultAjax resultAjax = new ResultAjax();
        resultAjax.setCode(code);
        resultAjax.setMsg(msg);
        resultAjax.setData(data);
        return resultAjax;
    }
}


之后业务中所有返回类型都设置为上述定义的统一返回对象:

@RestController
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/hello")
    public ResultAjax sayHello(){
        return ResultAjax.succ("hello");
    }
    @RequestMapping("/hi")
    public ResultAjax sayHi(){
        return ResultAjax.succ("hi");
    }
}


当然虽然做出了上面的约定,但也不能保证在之后的业务代码不会误用其他返回类型,这个时候就需要使用到统一返回值的保底策略了。可以在 @ControllerAdvice 注解的类中实现ResponseBodyAdvice 接口,对所有控制器方法的返回值进行统一的处理。


// 执行统一返回数据的保底策略
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {

    @Autowired
    private ObjectMapper objectMapper;

    //     * true -> 才会调用 beforeBodyWrite 方法,
    //     * 反之则永远不会调用 beforeBodyWrite 方法
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body,
                                  MethodParameter returnType,
                                  MediaType selectedContentType,
                                  Class selectedConverterType,
                                  ServerHttpRequest request,
                                  ServerHttpResponse response) {
        // 对返回值进行判断
        // 如果返回值和统一返回格式一致直接返回
        if (body instanceof ResultAjax) {
            return body;
        }
        // 对字符串返回格式进行单独判断处理
        if (body instanceof String) {
            ResultAjax resultAjax = ResultAjax.succ(body);
            try {
                return objectMapper.writeValueAsString(resultAjax);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }
        // 其他情况
        return ResultAjax.succ(body);
    }
}

代码解析:

  1. @ControllerAdvice 是一个注解,用于声明一个类为全局控制器增强器。在 @ControllerAdvice 注解的类中实现 ResponseBodyAdvice 接口,对所有控制器方法的返回值进行统一的处理
  2. 实现 supports 方法,判断当前返回类型是否需要进行响应体重写处理。由于这里返回 true,因此所有返回值都会被拦截并进行响应体重写处理。
  3. 实现 beforeBodyWrite 方法,对所有返回值进行统一的响应体处理。


四、@ControllerAdvice 实现原理(了解)


通过上面统一异常处理和统一数据返回格式的介绍,我们发现二者都使用到了 @ControllerAdvice 这个注解,下面我们简单介绍一下它的底层是怎么实现的:

@ControllerAdvice 它更像是一个全局的拦截器,可以对控制器的行为进行统一的处理和管理:


当我们点击 @ControllerAdvice 的源码,可以看到 @ControllerAdvice 同样派生于 @Component 组件,而所有组件初始化都会调用 InitializingBean 接口,其中 Spring MVC 中的实现的子类中有一afterPropertiesSet() 方法,表示所有的参数设置完成之后执行的方法,这个方法中又有一个 initControllerAdviceCache 方法,当程序执行到特定事件发生的时候,比如返回数据前或发生异常时,Spring会根据规则查找所有使用了@ControllerAdvice 注解的类,并调用其中对应的 Advice 方法来执行相应的业务逻辑。


相关文章
|
1月前
|
JavaScript 前端开发 Java
基于SpringBoot+Vue实现前后端交互功能(详解Vue框架机制)
基于SpringBoot+Vue实现前后端交互功能(详解Vue框架机制)
|
7天前
|
XML Java UED
使用 Spring Boot 实现重试和补偿功能:从理论到实践
【6月更文挑战第17天】在分布式系统中,服务之间的调用可能会因为网络故障、服务器负载等原因偶尔失败。为了提高系统的可靠性和稳定性,我们经常需要实现重试和补偿功能。
33 6
|
4天前
|
缓存 NoSQL Java
在 Spring Boot 应用中使用 Spring Cache 和 Redis 实现数据查询的缓存功能
在 Spring Boot 应用中使用 Spring Cache 和 Redis 实现数据查询的缓存功能
15 0
|
1月前
|
Java 测试技术 数据库
基于SpringBoot+HTML实现登录注册功能模块
基于SpringBoot+HTML实现登录注册功能模块
|
5天前
|
XML 缓存 Java
Spring Boot 优雅实现降级功能:Hystrix 与 Resilience4j 的实践
【6月更文挑战第19天】在分布式系统中,服务降级是一种重要的容错机制。当某个服务不可用或响应慢时,降级机制可以保证系统的整体稳定性。本文将详细介绍如何在 Spring Boot 中使用 Hystrix 和 Resilience4j 实现降级功能。
29 7
|
9天前
|
JavaScript Java 测试技术
基于SpringBoot+Vue+uniapp的多功能智能手机阅读APP的详细设计和实现
基于SpringBoot+Vue+uniapp的多功能智能手机阅读APP的详细设计和实现
36 12
|
6天前
|
NoSQL 算法 Java
使用 Spring Boot 实现限流功能:从理论到实践
【6月更文挑战第18天】在微服务和高并发系统中,限流(Rate Limiting)是一种非常重要的技术手段,用于保护系统免受过载,确保服务的稳定性。限流可以控制请求的速率,防止单个客户端或恶意用户消耗过多的资源,从而影响其他用户。
16 5
|
4天前
|
安全 Java Maven
在 Spring Boot 中实现邮件发送功能可以通过集成 Spring Boot 提供的邮件发送支持来完成
在 Spring Boot 中实现邮件发送功能可以通过集成 Spring Boot 提供的邮件发送支持来完成
11 2
|
4天前
|
缓存 监控 安全
在 Spring Boot 中使用 AOP(Aspect-Oriented Programming)实现日志记录功能
在 Spring Boot 中使用 AOP(Aspect-Oriented Programming)实现日志记录功能
16 1
|
4天前
|
监控 NoSQL Java
在 Spring Boot 中实现 Redis 的发布/订阅功能可以通过 RedisTemplate 和消息监听器来完成
在 Spring Boot 中实现 Redis 的发布/订阅功能可以通过 RedisTemplate 和消息监听器来完成
9 1