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 注解。
@ControllerAdvice: 标记在一个类上,表示这个类是一个“控制器增强”类。它可以捕获所有被@Controller或@RestController注解的类中抛出的异常。@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 如何实现?
- 创建拦截器类: 创建一个类,实现
HandlerInterceptor接口。 - 重写核心方法:
preHandle(): 在 Controller 方法执行前调用。返回true则继续执行,返回false则中断请求。postHandle(): 在 Controller 方法执行后,视图渲染前调用。可以修改ModelAndView。afterCompletion(): 在整个请求处理完毕(包括视图渲染)后调用。主要用于资源清理。
- 注册拦截器: 创建一个配置类,实现
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 才能正常执行。

浙公网安备 33010602011771号