Spring Boot - Web 开发进阶

Spring Boot 核心 —— Web 开发进阶

1. 统一异常处理 (@ControllerAdvice)

1.1 为什么需要统一异常处理?

在 Web 开发中,Controller 的方法在处理业务逻辑时可能会抛出各种异常。如果我们在每个 Controller 方法内部都使用 try-catch 来处理,会导致大量重复代码,非常不优雅。

Spring Boot 提供了一种强大的机制,允许我们创建一个全局的异常处理器,集中处理所有 Controller 抛出的异常。

  • 核心思想: 将异常处理逻辑从业务逻辑中剥离出来,实现解耦。
  • 生活比喻: 就像为整个公司(Web 应用)聘请了一个专业的危机公关团队 (@ControllerAdvice)。无论哪个部门(Controller)发生了什么类型的突发状况(抛出异常),都由这个公关团队统一对外发布声明(返回统一格式的错误响应),而不需要每个部门自己手忙脚乱地去处理。

1.2 如何实现?

通过组合使用 @ControllerAdvice 和 @ExceptionHandler 注解。

  1. @ControllerAdvice: 标记在一个类上,表示这个类是一个“控制器增强”类。它可以捕获所有被 @Controller 或 @RestController 注解的类中抛出的异常。
  2. @ExceptionHandler: 标记在 @ControllerAdvice 类的方法上,并指定它能处理的异常类型。当匹配的异常发生时,这个方法就会被调用。

1.3 代码示例

假设我们有一个自定义的业务异常 BusinessException。

// 1. 自定义一个业务异常
public class BusinessException extends RuntimeException {
    public BusinessException(String message) {
        super(message);
    }
}

// 2. 在 Controller 中抛出异常
@RestController
public class UserController {
    @GetMapping("/user/{id}")
    public User getUser(@PathVariable Long id) {
        if (id < 1) {
            throw new BusinessException("User ID must be positive!");
        }
        // ... 正常逻辑
        return new User(id, "testuser");
    }
}

// 3. 创建全局异常处理器
@ControllerAdvice
public class GlobalExceptionHandler {

    // 定义一个统一的错误响应结构
    public static class ErrorResponse {
        private int status;
        private String message;
        // Getters and Setters
    }

    // 处理我们自定义的 BusinessException
    @ExceptionHandler(BusinessException.class)
    @ResponseBody // 因为我们想返回 JSON 数据
    @ResponseStatus(HttpStatus.BAD_REQUEST) // 设置 HTTP 状态码为 400
    public ErrorResponse handleBusinessException(BusinessException ex) {
        ErrorResponse error = new ErrorResponse();
        error.setStatus(HttpStatus.BAD_REQUEST.value());
        error.setMessage(ex.getMessage());
        return error;
    }

    // 处理所有其他未被捕获的异常
    @ExceptionHandler(Exception.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) // 状态码为 500
    public ErrorResponse handleGlobalException(Exception ex) {
        ErrorResponse error = new ErrorResponse();
        error.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        error.setMessage("An unexpected error occurred: " + ex.getMessage());
        return error;
    }
}
  • 效果: 当访问 /user/0 时,getUser 方法会抛出 BusinessException,GlobalExceptionHandler 中的 handleBusinessException 方法会被触发,最终向客户端返回一个 HTTP 状态码为 400 的、格式统一的 JSON 错误信息。

2. 拦截器 (Interceptor)

2.1 为什么需要拦截器?

拦截器提供了一种机制,允许我们在请求处理过程中的特定点(请求到达 Controller 之前、之后,或视图渲染之后)执行自定义的逻辑。这对于处理一些通用的横切关注点非常有用,例如:

  • 登录验证

  • 权限检查

  • 性能监控,记录请求处理时间

  • 日志记录

  • 生活比喻: 拦截器就像大楼门口的保安。每个进入大楼的人(HTTP 请求),在到达他想去的办公室(Controller)之前,都必须先经过保安的盘问(preHandle)。保安可以决定是否放行。等人办完事出来后,保安还会做个记录(postHandle 和 afterCompletion)。

2.2 拦截器 vs. 过滤器 (Filter)

对比项 过滤器 (Filter) 拦截器 (Interceptor)
所属规范 Servlet 规范 Spring MVC 框架
执行时机 在 DispatcherServlet 之前 在 DispatcherServlet 之内,Controller 方法调用前后
访问能力 无法访问到要执行的 Controller 方法信息 可以访问到要执行的 Controller 方法信息
使用场景 字符编码、跨域等请求级别的通用处理 登录、权限等与业务处理器相关的逻辑

2.3 如何实现?

  1. 创建拦截器类: 创建一个类,实现 HandlerInterceptor 接口。
  2. 重写核心方法:
    • preHandle(): 在 Controller 方法执行前调用。返回 true 则继续执行,返回 false 则中断请求。
    • postHandle(): 在 Controller 方法执行后,视图渲染前调用。可以修改 ModelAndView。
    • afterCompletion(): 在整个请求处理完毕(包括视图渲染)后调用。主要用于资源清理。
  3. 注册拦截器: 创建一个配置类,实现 WebMvcConfigurer 接口,并重写 addInterceptors() 方法来注册我们的拦截器。

2.4 代码示例

// 1. 创建一个登录拦截器
@Component
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("LoginInterceptor: preHandle is called.");
        String token = request.getHeader("token");
        if (token == null || !token.equals("valid-token")) {
            response.setStatus(401); // Unauthorized
            response.getWriter().write("Unauthorized: Invalid Token");
            return false; // 中断请求
        }
        return true; // 放行
    }
}

// 2. 创建配置类来注册拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/api/**") // 指定要拦截的路径
                .excludePathPatterns("/api/login"); // 指定要排除的路径
    }
}
  • 效果: 当一个请求访问 /api/user-info 时,LoginInterceptor 会先执行。它会检查请求头中是否有合法的 token。如果没有,它会直接返回 401 错误,请求根本不会到达 Controller。如果 token 合法,则放行,Controller 才能正常执行。
posted @ 2026-01-21 16:22  我是刘瘦瘦  阅读(36)  评论(0)    收藏  举报