[Spring] Aop 注解执行顺序

背景

验证 @Around、@Before、@After、@AfterReturning、@AfterThrowing 执行顺序

代码

Annotation

package com.xxx.aop;

import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
	public @interface LogOperation {
}

Controller

package com.xxx.aop;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
@RequestMapping("/testAop")
public class TestAopController {

    @GetMapping("/get")
    @LogOperation
    public String get() {
        log.info("TestAopController#get");
        return "ok";
    }
}

Aspect

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Slf4j
@Aspect
@Component
public class LogOperationAspect {

    @Pointcut("@annotation(com.xxx.aop.LogOperation)")
    private void methodAnnotation() {
    }

    @Around("methodAnnotation()")
    public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        log.info("LogOperationAspect#aroundAdvice aroundBefore");

        Object result = joinPoint.proceed();

        log.info("LogOperationAspect#aroundAdvice aroundAfter");

        long end = System.currentTimeMillis();
        log.info("LogOperationAspect#aroundAdvice method:{}, cost:{} ms", method.getName(), end - start);

        return result;
    }

    @Before("methodAnnotation()")
    public void beforeAdvice(JoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        LogOperation annotation = method.getAnnotation(LogOperation.class);
        log.info("LogOperationAspect#beforeAdvice execute");
    }

    @After("methodAnnotation()")
    public void afterAdvice(JoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        LogOperation annotation = method.getAnnotation(LogOperation.class);
        log.info("LogOperationAspect#afterAdvice execute");
    }

    @AfterReturning("methodAnnotation()")
    public void afterReturningAdvice(JoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        LogOperation annotation = method.getAnnotation(LogOperation.class);
        log.info("LogOperationAspect#afterReturningAdvice execute");
    }
}

执行结果

AopResult

结论

  1. @Around注解方法的前半部分业务逻辑
  2. @Before注解方法的业务逻辑
  3. 目标方法的业务逻辑
  4. @AfterThrowing(若目标方法有异常,执行@AfterThrowing注解方法的业务逻辑)
  5. @AfterReturning(若目标方法无异常,执行@AfterReturning注解方法的业务逻辑)
  6. @After(不管目标方法有无异常,都会执行@After注解方法的业务逻辑)
  7. @Around注解方法的后半部分业务逻辑
    tips;本文未演示@AfterThrowing注解执行顺序
### AOP注解的使用方法与原理 AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,旨在通过分离横切关注点(如日志记录、事务管理等)来增强代码的模块化[^1]。Spring框架中的AOP主要依赖于注解和XML配置两种方式实现,其中注解驱动的方式因其简洁性和灵活性而被广泛采用。 #### 1. AOP注解的核心概念 AOP注解的核心包括以下几个部分: - **切面(Aspect)**:定义切面的类或方法,通常使用`@Aspect`注解标记。 - **切入点(Pointcut)**:定义程序中需要拦截的具体位置,例如某个方法的执行。 - **通知(Advice)**:定义在特定切入点处执行的操作,分为前置通知、后置通知、环绕通知等。 #### 2. 常见AOP注解及其含义 以下是一些常用的AOP注解及其功能: - **@Aspect** - 标记一个类为切面类,表示该类包含AOP相关的逻辑[^1]。 - 示例: ```java @Aspect public class LoggingAspect { // 切入点和通知定义 } ``` - **@Before** - 定义前置通知,在目标方法执行之前触发。 - 示例: ```java @Before("execution(* com.example.service.*.*(..))") public void logBefore(JoinPoint joinPoint) { System.out.println("Method " + joinPoint.getSignature().getName() + " is executing."); } ``` - **@After** - 定义后置通知,在目标方法执行之后触发,无论方法是否抛出异常。 - 示例: ```java @After("execution(* com.example.service.*.*(..))") public void logAfter(JoinPoint joinPoint) { System.out.println("Method " + joinPoint.getSignature().getName() + " has finished."); } ``` - **@AfterReturning** - 定义返回通知,在目标方法成功返回后触发,可以获取返回值。 - 示例: ```java @AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result") public void logReturn(Object result) { System.out.println("Method returned with value: " + result); } ``` - **@AfterThrowing** - 定义异常通知,在目标方法抛出异常时触发,可以捕获异常信息。 - 示例: ```java @AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex") public void logException(Exception ex) { System.out.println("Exception occurred: " + ex.getMessage()); } ``` - **@Around** - 定义环绕通知,允许在目标方法执行前后进行自定义操作,甚至可以控制方法是否执行。 - 示例: ```java @Around("execution(* com.example.service.*.*(..))") public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("Method " + joinPoint.getSignature().getName() + " is about to execute."); Object result = joinPoint.proceed(); // 执行目标方法 System.out.println("Method " + joinPoint.getSignature().getName() + " has finished."); return result; } ``` #### 3. 切入点表达式的语法 切入点表达式用于定义匹配规则,确定哪些方法会被拦截。最常见的表达式是`execution`,其语法如下: ```plaintext execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) ``` - `ret-type-pattern`:返回值类型模式,`*`表示任意返回值。 - `declaring-type-pattern`:声明类型模式,`..`表示任意包路径。 - `name-pattern`:方法名模式,支持通配符`*`。 - `param-pattern`:参数列表模式,`(..)`表示任意参数。 示例: ```java @Pointcut("execution(* com.example.service.*.*(..))") public void serviceMethods() {} ``` 上述表达式匹配`com.example.service`包下所有类的所有方法。 #### 4. 配置AOP注解支持 为了启用AOP注解支持,需要在Spring配置文件中添加以下内容: ```xml <aop:aspectj-autoproxy /> ``` 或者在Java配置类中使用: ```java @Configuration @EnableAspectJAutoProxy public class AppConfig { // 配置Bean } ``` --- ### 示例:使用Spring AOP实现日志记录 以下是使用Spring AOP实现日志记录的一个完整示例: 1. **引入依赖** 在`pom.xml`中添加AOP相关依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> ``` 2. **定义业务逻辑** ```java public interface Calculator { int add(int a, int b); } @Service public class CalculatorImpl implements Calculator { @Override public int add(int a, int b) { return a + b; } } ``` 3. **定义切面和通知** ```java @Aspect @Component public class LoggingAspect { @Before("execution(* com.example.service.Calculator.add(..))") public void logBefore(JoinPoint joinPoint) { System.out.println("Method " + joinPoint.getSignature().getName() + " is executing."); } @AfterReturning(pointcut = "execution(* com.example.service.Calculator.add(..))", returning = "result") public void logReturn(Object result) { System.out.println("Method returned with value: " + result); } } ``` 4. **运行示例** 调用`Calculator.add`方法时,会自动打印日志信息。 ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值