[Java实战]springboot注解@ControllerAdvice解析(十二)
[Java实战]springboot注解@ControllerAdvice解析(十二)
一、引言
在现代的 Spring Boot 应用程序开发中,我们追求代码的简洁性、可维护性和可扩展性。而 @ControllerAdvice
注解就像是为控制器层量身定制的“魔法棒”,它能够帮助我们优雅地处理全局异常、统一响应格式,还能对控制器进行一些通用的增强操作。本文将深入探讨 @ControllerAdvice
的使用场景,带你领略其在 Spring Boot 项目中的强大魅力。
二、全局异常处理
2.1 背景
在传统的 Spring MVC 项目中,我们常常会为每个控制器方法编写单独的异常处理逻辑。例如,当一个方法抛出 NullPointerException
时,我们可能需要在该方法内部捕获异常并返回一个友好的错误提示。但这种方式会导致代码冗余,且难以维护。一旦需要修改异常处理逻辑,我们可能需要逐个修改每个控制器方法中的异常处理代码。
2.2 @ControllerAdvice 的解决方案
@ControllerAdvice
注解可以配合 @ExceptionHandler
注解来实现全局异常处理。我们只需要定义一个带有 @ControllerAdvice
注解的类,在这个类中通过 @ExceptionHandler
注解标注的方法来捕获特定类型的异常,并返回统一的响应格式。
@ControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(NullPointerException.class)public ResponseEntity<String> handleNullPointerException(NullPointerException e) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("发生空指针异常:" + e.getMessage());}@ExceptionHandler(IllegalArgumentException.class)public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException e) {return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("非法参数异常:" + e.getMessage());}
}
在上面的代码中,GlobalExceptionHandler
类通过 @ControllerAdvice
注解标记为一个全局异常处理器。当项目中任何控制器方法抛出 NullPointerException
或 IllegalArgumentException
时,都会被相应的异常处理方法捕获,并返回相应的 HTTP 状态码和错误信息。这种方式大大减少了代码冗余,提高了代码的可维护性。
三、统一响应格式
3.1 问题
在开发 RESTful API 时,我们希望所有的响应都遵循统一的格式,例如包含状态码、消息和数据等字段。如果没有 @ControllerAdvice
,我们可能需要在每个控制器方法中手动构造这样的响应格式,这同样会导致代码重复。
3.2 使用 @ControllerAdvice 实现
@ControllerAdvice
可以通过 @ModelAttribute
注解来实现对控制器方法返回值的统一包装。我们可以定义一个通用的响应类,然后在 @ControllerAdvice
标注的类中使用 @ModelAttribute
注解来将控制器方法的返回值包装成我们定义的通用响应格式。
public class ApiResponse<T> {private int code;private String message;private T data;// 省略构造方法、getter 和 setter 方法
}@ControllerAdvice
public class GlobalResponseAdvice {@ModelAttributepublic ApiResponse<?> addApiResponseHandler(@ModelAttribute Object data) {if (data instanceof ApiResponse<?>) {return (ApiResponse<?>) data;} else {return new ApiResponse<>(200, "操作成功", data);}}
}
在这个例子中,GlobalResponseAdvice
类通过 @ModelAttribute
注解的方法 addApiResponseHandler
来处理控制器方法的返回值。如果返回值已经是一个 ApiResponse
类型的对象,就直接返回;否则,将返回值包装成一个状态码为 200、消息为“操作成功”的 ApiResponse
对象。这样,我们就可以确保所有的控制器方法返回的响应都遵循统一的格式,方便前端进行处理。
四、控制器方法增强
4.1 需求
有时候,我们希望在控制器方法执行前后进行一些通用的操作,比如记录日志、验证用户权限等。如果没有合适的工具,我们可能需要在每个控制器方法中手动添加这些逻辑,这不仅增加了代码量,还降低了代码的可读性。
4.2 @ControllerAdvice 的实现方式
@ControllerAdvice
注解可以与 @Before
、@After
、@AfterReturning
、@AfterThrowing
和 @Around
等 AOP 注解配合使用,来实现对控制器方法的增强。
@ControllerAdvice
public class ControllerMethodAdvice {private final Logger logger = LoggerFactory.getLogger(ControllerMethodAdvice.class);@Before("execution(* com.example.controller.*.*(..))")public void beforeMethod(JoinPoint joinPoint) {logger.info("方法执行前:{}", joinPoint.getSignature().getName());}@After("execution(* com.example.controller.*.*(..))")public void afterMethod(JoinPoint joinPoint) {logger.info("方法执行后:{}", joinPoint.getSignature().getName());}@AfterReturning(pointcut = "execution(* com.example.controller.*.*(..))", returning = "result")public void afterReturningMethod(JoinPoint joinPoint, Object result) {logger.info("方法返回值:{}", result);}@AfterThrowing(pointcut = "execution(* com.example.controller.*.*(..))", throwing = "ex")public void afterThrowingMethod(JoinPoint joinPoint, Exception ex) {logger.error("方法抛出异常:{}", ex.getMessage());}
}
在上面的代码中,我们通过 @ControllerAdvice
注解定义了一个增强类 ControllerMethodAdvice
,并使用 AOP 注解来实现对控制器方法的增强。@Before
注解的方法会在控制器方法执行前记录方法名称,@After
注解的方法会在控制器方法执行后记录方法名称,@AfterReturning
注解的方法会在控制器方法正常返回后记录返回值,@AfterThrowing
注解的方法会在控制器方法抛出异常后记录异常信息。这样,我们就可以在不修改控制器方法代码的情况下,实现对控制器方法的通用增强。
五、跨多个控制器共享数据
5.1 场景
在一些复杂的业务场景中,我们可能需要在多个控制器之间共享一些数据,比如用户信息、配置参数等。如果没有合适的机制,我们可能需要在每个控制器中重复获取这些数据,这不仅增加了代码量,还可能导致数据不一致的问题。
5.2 @ControllerAdvice 的应用
@ControllerAdvice
注解可以通过 @ModelAttribute
注解来向模型中添加共享数据,这些数据可以在多个控制器中使用。
@ControllerAdvice
public class SharedDataAdvice {@ModelAttribute("sharedData")public String addSharedData() {return "这是共享数据";}
}
在上面的代码中,SharedDataAdvice
类通过 @ModelAttribute
注解的方法 addSharedData
向模型中添加了一个名为 sharedData
的共享数据。在任何控制器的方法中,我们都可以通过 Model
对象来获取这个共享数据,而不需要重复获取。
@Controller
public class TestController {@GetMapping("/test")public String test(Model model) {String sharedData = model.getAttribute("sharedData").toString();return "共享数据:" + sharedData;}
}
在 TestController
的 test
方法中,我们通过 Model
对象获取了名为 sharedData
的共享数据,并将其返回给前端。
六、总结
@ControllerAdvice
注解在 Spring Boot 项目中有着广泛的应用场景。它不仅可以实现全局异常处理,减少代码冗余,提高代码的可维护性;还可以统一响应格式,方便前端进行处理;还能对控制器方法进行增强,实现通用的日志记录、权限验证等功能;并且可以实现跨多个控制器共享数据,避免重复获取数据。总之,@ControllerAdvice
是一个非常实用的注解,它能够帮助我们编写出更加优雅、简洁、可维护的代码。在实际开发中,我们应该充分了解并合理利用 @ControllerAdvice
的这些功能,提升我们的开发效率和代码质量。