java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Spring Boot 横切逻辑

Spring Boot 中的横切逻辑统一治理方案

作者:手握风云-

拦截器是Spring框架提供的核心功能之一,核心用于统一拦截用户的请求,文章主要介绍了Spring框架中的拦截器、统一数据返回格式和统一异常处理,本文给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧

一、拦截器

        拦截器是 Spring 框架提供的核心功能之一,核心用于统一拦截用户的请求,可在目标方法执行前后执行预先设定的通用代码,也能在请求执行前阻止其继续运行,常用来实现登录校验、日志记录等通用操作,是解决接口通用功能处理的高效方式,其入门核心围绕定义拦截器注册配置拦截器两步展开。

1.1. 拦截器入门

1. 拦截器核心定义

        拦截器允许开发人员在应用程序中对请求做通用性处理,在用户请求响应的前后插入自定义逻辑,也可根据业务需求拦截请求(如判断 Session 中是否有登录信息,无则拦截请求,有则放行)。就如同学校的保安,他需要判断哪些人能进,哪些人不能进。

        拦截器的实现需要完成自定义拦截器注册配置拦截器两个核心步骤,缺一不可,且需通过指定注解让 Spring 容器管理相关类。自定义拦截器相当于告知保安的工作,注册配置拦截器相当于将保安放到指定的岗位上。

2. 定义拦截器

package org.springframework.web.servlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jspecify.annotations.Nullable;
public interface HandlerInterceptor {
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }
    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
    }
    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
    }
}

        自定义类实现 Spring 提供的 HandlerInterceptor 接口,并重写里面的方法。preHandle 方法在请求到达具体的控制器方法之前执行,它是拦截器中最先被调用的方法。该方法返回一个布尔值,如果返回 true,表示请求继续向下传递,进入后续的拦截器或控制器;如果返回 false,则表示请求被终止。postHandle 方法在控制器方法执行完毕之后,但在视图渲染之前执行。这意味着在这个阶段,开发者已经可以访问到控制器处理后的 ModelAndView 对象,因此常用于对模型数据进行统一的修改或补充,或者根据不同的视图需求进行相应的处理。afterCompletion 方法在整个请求处理完成之后执行,即在视图渲染结束后进行回调。无论请求是否成功,只要 preHandle 返回了 true,该方法最终都会被执行。

3. 注册配置拦截器

package com.yang.test2_22_1.config;
import com.yang.test2_22_1.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**");
    }
}

1.2. 拦截器详解

1. 拦截路径

        拦截路径用于指定自定义拦截器对哪些请求生效,在注册配置拦截器时通过两个核心方法配置:addPathPatterns(),指定需要拦截的请求路径;excludePathPatterns(),指定需要排除、不进行拦截的请求路径。

        拦截路径支持通配符匹配,核心规则如下表,规则可拦截项目中所有 URL(包括图片、JS、CSS 等静态文件):

拦截路径含义
/*一级路径
/**任意级路径
/api/*/api下的一级路径
/api/**/api下的任意级路径
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**") // 拦截所有请求
                .excludePathPatterns("/api/login"); // 排除登录请求,不拦截
    }
}

2. 拦截器执行流程

        正常的调用顺序:

        有了拦截器之后的调用顺序:

        拦截器核心作用是在Controller层方法前后插入自定义逻辑,其执行流程依赖HandlerInterceptor 接口三个核心方法,与SpringMVC请求流转紧密相关。无拦截器时,请求按“用户请求→Controller→Service→Mapper→数据库”流转,处理后原路返回。添加拦截器后,请求先被拦截:到达Controller前执行preHandle(),返回true放行继续,返回false中断;若preHandle()返回true,请求正常流转,Controller目标方法执行完、视图渲染前执行postHandle()(后端少用);视图渲染完毕执行afterCompletion()用于收尾。核心方法执行顺序为preHandle()(Controller前)→目标Controller方法→postHandle()(Controller后、视图渲染前)→afterCompletion()(视图渲染后),postHandle() 和 afterCompletion() 仅在 preHandle() 返回true时执行。 

1.3. 登录校验

package com.yang.test2_22_1.controller;
import com.yang.test2_22_1.model.User;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api")
public class LoginController {
    @PostMapping("/login")
    public Map<String, Object> processLogin(@RequestBody User user) {
        Map<String, Object> response = new HashMap<>();
        String validUsername = "admin";
        String validPassword = "password123";
        if (validUsername.equals(user.getUsername()) && validPassword.equals(user.getPassword())) {
            response.put("success", true);
            response.put("message", "登录成功!欢迎回来," + user.getUsername() + "。");
        } else {
            response.put("success", false);
            response.put("message", "用户名或密码错误,请重试。");
        }
        return response;
    }
    @GetMapping("/secret")
    public String getSecretInfo() {
        return "这是一条机密信息,只有登录后的用户才能看到!";
    }
}
package com.yang.test2_22_1.interceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.jspecify.annotations.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Object loginUser = request.getSession().getAttribute("LOGIN_USER");
        if (loginUser == null) {
            response.setStatus(HttpServletResponse.SC_ACCEPTED);
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write("{\"error\": \"未登录,请先登录系统\"}");
            return false;
        }
        log.info("执行 preHandle 方法");
        return true;
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
        log.info("执行 postHandle 方法");
    }
}
package com.yang.test2_22_1.config;
import com.yang.test2_22_1.interceptor.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/api/login", "/api/secret");
    }
}

二、统一数据返回格式

2.1. 快速入门

        统一的数据返回格式通过 @ControllerAdvice + ResponseBodyAdvice 组合实现,核心是对所有接口的返回结果进行统一封装,简化前后端交互。

        @ControllerAdvice:控制器通知类,用于全局拦截控制器的返回结果和异常,是实现统一处理的基础注解;ResponseBodyAdvice:响应体增强接口,提供对返回数据的拦截和修改能力,需重写其核心方法。@RestControllerAdvice 注解是包含了 @ControllerAdvice 和 ResponseBodyAdvice,方法返回值直接写入 HTTP 响应体 (Body),通常转换为 JSON/XML 数据。

import com.example.demo.model.Result;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    // 判断是否执行beforeBodyWrite方法(true=执行,false=不执行)
    @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) {
        return Result.success(body); // 调用Result工具类封装数据
    }
}

        supports 方法作用是控制哪些接口的返回结果需要被统一处理,并且可通过 returnType 获取当前执行的类名和方法名,实现精准匹配。

// 获取当前执行的控制器类
Class<?> declaringClass = returnType.getMethod().getDeclaringClass();
// 获取当前执行的方法
Method method = returnType.getMethod();

        beforeBodyWrite 方法作用是对接口返回的原始数据(body)进行封装,返回统一格式的 Result 对象。依赖 Result 工具类:需提前定义 Result 泛型类,包含 status(状态)、errorMessage(错误信息)、data(业务数据)三个核心字段。

三、统一异常处理

        SpringBoot 中统一异常处理核心通过 @ControllerAdvice + @ExceptionHandler 注解组合实现,实现了项目中异常的统一捕获、处理和返回格式标准化,避免零散的异常处理逻辑,降低前后端沟通和开发成本。

3.1. 核心注解

3.2. 基础实现方式

3.3. 异常匹配顺序

        当存在多个异常处理方法时,优先匹配最具体的异常类型,匹配规则为:当前异常类及其子类向上依次匹配

package com.yang.test2_25_1.exception;
import com.yang.test2_25_1.common.ApiResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    /**
     * 处理用户未找到异常
     */
    @ExceptionHandler(UserNotFoundException.class)
    public ApiResponse<Void> handleUserNotFoundException(UserNotFoundException e) {
        log.warn("参数异常:{}", e.getMessage());
        return ApiResponse.error(404, e.getMessage());
    }
    /**
     * 处理参数异常
     */
    @ExceptionHandler(IllegalArgumentException.class)
    public ApiResponse<Void> handleIllegalArgumentException(IllegalArgumentException e) {
        log.warn("参数异常:{}", e.getMessage());
        return ApiResponse.error(400, e.getMessage());
    }
    /**
     * 处理所有异常
     */
    @ExceptionHandler(Exception.class)
    public ApiResponse<Void> handleException(Exception e) {
        log.error("系统内部发生未知异常", e.getMessage());
        return ApiResponse.error(500, "服务器内部开小差了,请稍后再试");
    }
}

3.4. 核心效果

到此这篇关于Spring Boot 中的横切逻辑统一治理方案的文章就介绍到这了,更多相关Spring Boot 横切逻辑内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文