面向切面编程(AOP)中的“通知”和 专门用于增强 Spring MVC 中控制器(Controller)的“通知”

在 Spring Boot 中,带有 "Advice" 后缀的注解和类确实容易让人混淆。简单来说,它们可以分为两大阵营:面向切面编程(AOP)中的"通知"专门用于增强 Spring MVC 中控制器(Controller)的"通知"

为了让你快速建立一个整体印象,下面这个表格清晰地展示了它们的核心区别。

特性维度 AOP 中的 "Advice" (如 @Before, @Around) Controller 增强器 @ControllerAdvice
所属技术 AOP(面向切面编程) Spring MVC(Web层)
作用目标 任何 Spring Bean 的方法(如 Service, Repository 的方法) @Controller/@RestController中的方法
核心目的 解耦横切关注点(如日志、事务、权限) 统一处理 Web 控制器的异常、数据、参数
关键注解/类 @Before, @After, @Around, @AfterReturning, @AfterThrowing @ControllerAdvice, @RestControllerAdvice

接下来,我们深入了解一下每一类的具体职责和应用。

🛠️ AOP 中的 "Advice"(通知)

AOP 中的通知允许你在目标方法执行的特定"点"注入代码,这些"点"被称为"连接点"。Spring AOP 主要支持五种类型的通知,它们像钩子一样挂在方法调用的不同位置 。

下面的时序图直观地展示了一个方法被AOP增强后,各种通知的执行时机:

下面是这五种通知的详细说明和简单代码示例 :

  1. 前置通知 (@Before)

    • 时机 :在目标方法执行之前运行。
    • 应用:常用于参数校验、权限检查等。
    less 复制代码
    @Aspect
    @Component
    public class LoggingAspect {
        @Before("execution(* com.example.service.*.*(..))")
        public void logBefore(JoinPoint joinPoint) {
            System.out.println("即将执行方法: " + joinPoint.getSignature().getName());
        }
    }
  2. 后置通知 (@After)

    • 时机 :在目标方法执行之后 运行,无论方法是正常返回还是抛出异常。类似于 finally块。
    • 应用:常用于资源清理。
    less 复制代码
    @Aspect
    @Component
    public class CleanupAspect {
        @After("execution(* com.example.service.*.*(..))")
        public void doCleanup(JoinPoint joinPoint) {
            System.out.println("方法执行完毕,进行资源清理: " + joinPoint.getSignature().getName());
        }
    }
  3. 返回通知 (@AfterReturning)

    • 时机 :在目标方法成功执行并正常返回后运行。
    • 应用:可以访问方法的返回值,常用于记录操作结果。
    less 复制代码
    @Aspect
    @Component
    public class MonitoringAspect {
        @AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
        public void logAfterReturning(JoinPoint joinPoint, Object result) {
            System.out.println("方法 " + joinPoint.getSignature().getName() + " 成功返回,结果为: " + result);
        }
    }
  4. 异常通知 (@AfterThrowing)

    • 时机 :在目标方法抛出异常后运行。
    • 应用:用于记录异常日志、告警等。
    less 复制代码
    @Aspect
    @Component
    public class ExceptionHandlingAspect {
        @AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex")
        public void logAfterThrowing(JoinPoint joinPoint, Exception ex) {
            System.err.println("方法 " + joinPoint.getSignature().getName() + " 抛出异常: " + ex.getMessage());
        }
    }
  5. 环绕通知 (@Around)

    • 时机 :最强大的通知,它包围了整个方法调用。可以手动决定是否执行目标方法,以及如何处理方法的返回值和异常。
    • 应用:性能监控、缓存、事务管理等。
    java 复制代码
    @Aspect
    @Component
    public class PerformanceAspect {
        @Around("execution(* com.example.service.*.*(..))")
        public Object measureExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
            long start = System.currentTimeMillis();
            try {
                // 执行目标方法
                return joinPoint.proceed();
            } finally {
                long duration = System.currentTimeMillis() - start;
                System.out.println("方法 " + joinPoint.getSignature().getName() + " 执行耗时: " + duration + "ms");
            }
        }
    }

🌐 Controller 增强器 @ControllerAdvice

@ControllerAdvice(及其组合注解 @RestControllerAdvice)是一个专门为 Spring MVC 控制器 设计的增强器。它本身不是基于 AOP 的代理机制,而是 Spring MVC 框架提供的一种内置功能,用于全局性地处理所有控制器的通用逻辑 。它主要与三个注解组合来实现三大功能:

  1. 全局异常处理 (@ExceptionHandler)

    这是 @ControllerAdvice最常用的功能。你可以在一个地方集中处理所有控制器中抛出的异常,返回统一的 JSON 错误格式或错误页面,避免在每个控制器中重复编写 try-catch。

    kotlin 复制代码
    @RestControllerAdvice // 等同于 @ControllerAdvice + @ResponseBody
    public class GlobalExceptionHandler {
        @ExceptionHandler(BizException.class)
        public Result handleBizException(BizException e) {
            // 处理自定义业务异常
            return Result.error(e.getCode(), e.getMessage());
        }
        @ExceptionHandler(Exception.class)
        public Result handleException(Exception e) {
            // 处理其他所有未精确定义的异常
            return Result.error("500", "系统繁忙");
        }
    }
  2. 全局数据绑定 (@ModelAttribute)

    这个方法返回的数据会自动添加到所有控制器的 Model 中,使得在每个视图(如 JSP、Thymeleaf 模板)或控制器方法中都能获取到这些公共数据。

    typescript 复制代码
    @ControllerAdvice
    public class GlobalDataBinder {
        @ModelAttribute("globalData")
        public Map<String, Object> globalData() {
            Map<String, Object> map = new HashMap<>();
            map.put("siteName", "我的应用");
            map.put("currentYear", Calendar.getInstance().get(Calendar.YEAR));
            return map;
        }
    }
  3. 全局数据预处理 (@InitBinder)

    用于全局定制请求参数绑定的规则,例如设置日期格式、忽略特定字段等。

    typescript 复制代码
    @ControllerAdvice
    public class GlobalDataPreAdvice {
        @InitBinder
        public void handleInitBinder(WebDataBinder dataBinder) {
            // 设置日期格式
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
            dataBinder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
        }
    }

    此外,@ControllerAdvice可以通过 basePackages, annotations, assignableTypes等属性来限制其作用范围,只增强特定的控制器 。

💡 如何选择?

  • 当你需要对业务方法 (如 Service 层的方法)添加日志、事务、权限等横切关注点 时,使用 AOP 的通知 (如 @Around)。
  • 当你需要对 Web 控制器 进行统一的异常处理、数据绑定或参数预处理 时,使用 @ControllerAdvice
相关推荐
逻极2 小时前
Rust数据类型(下):复合类型详解
开发语言·后端·rust
星释2 小时前
Rust 练习册 12:所有权系统
开发语言·后端·rust
间彧2 小时前
AOP中的五种通知类型在实际项目中如何选择?举例说明各自的典型应用场景
后端
间彧2 小时前
Spring Boot中很多Advice后缀的注解和类,都是干什么的
后端
披着羊皮不是狼2 小时前
Spring Boot——从零开始写一个接口:项目构建 + 分层实战
java·spring boot·后端·分层
Tony Bai3 小时前
Go GUI 开发的“绝境”与“破局”:2025 年现状与展望
开发语言·后端·golang
Tony Bai3 小时前
【Go模块构建与依赖管理】08 深入 Go Module Proxy 协议
开发语言·后端·golang
码事漫谈4 小时前
从一个问题深入解析C++字符串处理中的栈损坏
后端
码事漫谈4 小时前
C++ 核心基石:深入理解 RAII 思想,告别资源泄露的噩梦
后端