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 方法会抛出 BusinessExceptionGlobalExceptionHandler 中的 handleBusinessException 方法会被触发,最终向客户端返回一个 HTTP 状态码为 400 的、格式统一的 JSON 错误信息。

2. 拦截器 (Interceptor)

2.1 为什么需要拦截器?

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

  • 登录验证

  • 权限检查

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

  • 日志记录

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

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  我是刘瘦瘦  阅读(2)  评论(0)    收藏  举报