SpringBoot应用AOP及各注解的执行顺序

本文详细介绍如何在Spring Boot项目中使用AOP(面向切面编程),包括POM文件配置、创建切面类、定义切点及通知类型,以及通过配置类整合切面与目标类。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先第一步,POM引入jar

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>

		<dependency>
			<groupId>aspectj</groupId>
			<artifactId>aspectjweaver</artifactId>
			<version>1.5.3</version>
		</dependency>

第二步随便写一个类,然后类中写一个方法,我这里是写了一个除法运算:

public class MathCalculator {

    public int div (int i, int j) {
        System.out.println("MathCalculator.div...");
        return i / j;
    }
}

第三步写一个切面类:

//该注解表示声明该类为一个切面类
@Aspect
public class LogAspects {

    //定义一个切点,表达式可以灵活运用,我这个表达式是表示MathCalculator类中所有的方法都进行切入
    @Pointcut("execution(public int com.example.demo.aop.MathCalculator.*(..))")
    public void pointCut () {}

    //方法执行开始之前
    @Before("pointCut()")
    public void logStart (JoinPoint joinPoint) {
        System.out.println("除法运行...参数:{"+ Arrays.asList(joinPoint.getArgs())+"}");
    }

    //方法执行开始之后
    @After("pointCut()")
    public void logEnd (JoinPoint joinPoint) {
        System.out.println("除法结束..." + joinPoint.getSignature().getName());
    }

    //当方法进行返回的时候,returning属性是指定方法参数中的result来接收返回参数,这样就可以修改返回参数
    @AfterReturning(value = "pointCut()", returning = "result")
    public void logReturn (JoinPoint joinPoint, Object result) {
        System.out.println("除法正常返回... 返回结果:{"+result+"}");
    }

    //当方法执行异常的时候,throwding是指定方法参数中的e来接收异常参数,可以查看发生的什么异常
    @AfterThrowing(value = "pointCut()", throwing = "e")
    public void logException (JoinPoint joinPoint, Exception e) {
        System.out.println("异常... 异常信息:{"+e+"}");
    }

    //环绕通知
    @Around("pointCut()")
    public Object logAround (ProceedingJoinPoint joinPoint) throws Throwable {
        //原方法执行之前会打印这个日志
        System.out.println("环绕通知...  开始");
        //执行原方法
        Object obj = joinPoint.proceed();
        //原方法执行结束,打印这行日志
        System.out.println("环绕通知...  结束");
        //返回方法返回参数
        return obj;
    }
}

这里要注意一下AOP注解执行的先后顺序

环绕通知...  开始
除法运行...参数:{[1, 1]}
MathCalculator.div...
环绕通知...  结束
除法结束...div
除法正常返回... 返回结果:{1}

运行的顺序是

1、@Around

2、@Before

3、原方法

4、@Around

5、@After

6、@AfterReturning

第四步,写一个配置类,记得一定要加@EnableAspectJAutoProxy注解

//该配置类是为了将我们前2个类(切面类、被切面类)加入到Spring容器中
@Configuration
//启动AOP(一定要加这个注解,切记!!!)
@EnableAspectJAutoProxy
public class SpringOfAOPConfig {

    @Bean
    public MathCalculator mathCalculator () {
        return new MathCalculator();
    }

    @Bean
    public LogAspects logAspects () {
        return new LogAspects();
    }

}

第五步,测试:

@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {

	@Test
	public void contextLoads() {
	    //根据配置类获取SPring容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringOfAOPConfig.class);
        //从容器中获取Bean
        MathCalculator mathCalculator = applicationContext.getBean(MathCalculator.class);
        //执行除法运算
        mathCalculator.div(1, 1);
        //关闭容器
        applicationContext.close();
    }

}

 

 

正常结果:

2018-12-11 11:01:25.496  INFO 16364 --- [           main] com.example.demo.DemoApplicationTests    : Started DemoApplicationTests in 3.455 seconds (JVM running for 4.972)
环绕通知...  开始
除法运行...参数:{[1, 1]}
MathCalculator.div...
环绕通知...  结束
除法结束...div
除法正常返回... 返回结果:{1}
2018-12-11 11:01:25.807  INFO 16364 --- [       Thread-2] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'

异常结果:

2018-12-11 11:11:22.205  INFO 9628 --- [           main] com.example.demo.DemoApplicationTests    : Started DemoApplicationTests in 3.532 seconds (JVM running for 4.559)
环绕通知...  开始
除法运行...参数:{[1, 0]}
MathCalculator.div...
除法结束...div
异常... 异常信息:{java.lang.ArithmeticException: / by zero}

java.lang.ArithmeticException: / by zero

看到没:

发生异常的时候@Around的方法执行后切入没有进行,但是@After的方法却执行了,所以原方法发生异常后顺序就是

1、@Around

2、@Brfore

3、原方法

4、@After

5、@AfterThrowing

 

<think>我们参考了站内引用中的多个例子,特别是引用[1]和引用[3]中关于自定义注解AOP切面的实现。下面将详细说明在Spring Boot中如何通过AOP实现自定义注解的功能。 步骤分为: 1. 创建自定义注解 2. 创建切面类(使用@Aspect和@Component) 3. 在切面类中定义切入点(使用自定义注解)和通知(如@Before, @Around等) 4. 在需要的地方使用自定义注解 下面以记录方法日志为例,逐步实现: 第一步:创建自定义注解 我们可以定义一个注解,用于标记需要增强的方法。 ```java import java.lang.annotation.*; @Target(ElementType.METHOD) // 该注解只能应用在方法上 @Retention(RetentionPolicy.RUNTIME) // 在运行时保留,这样AOP代理才能读取到 @Documented public @interface Loggable { // 可以定义一些属性,比如日志描述信息 String value() default ""; } ``` 第二步:创建切面类 在切面类中,我们将定义通知(Advice)和切入点(Pointcut),切入点将使用我们自定义的注解。 ```java import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Aspect @Component public class LoggingAspect { // 定义切入点:所有被@Loggable注解的方法 @Pointcut("@annotation(com.example.annotation.Loggable)") public void loggableMethod() {} // 前置通知:在目标方法执行执行 @Before("loggableMethod()") public void logBefore(JoinPoint joinPoint) { // 这里可以获取方法名、参数等信息 String methodName = joinPoint.getSignature().getName(); Object[] args = joinPoint.getArgs(); System.out.println("方法 " + methodName + " 开始执行,参数: " + Arrays.toString(args)); } } ``` 第三步:在需要的方法上使用自定义注解 例如,我们在一个Service的方法上使用@Loggable注解。 ```java @Service public class UserService { @Loggable("这是一个获取用户信息的方法") public User getUserById(Long id) { // 业务逻辑 return new User(id, "张三"); } } ``` 这样,当调用getUserById方法时,切面中的前置通知就会执行,打印日志。 扩展:我们还可以使用环绕通知(@Around)来实现更复杂的逻辑,比如记录方法执行时间。 ```java import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; @Aspect @Component public class PerformanceAspect { @Around("@annotation(com.example.annotation.Loggable)") public Object logPerformance(ProceedingJoinPoint joinPoint) throws Throwable { long startTime = System.currentTimeMillis(); Object result = joinPoint.proceed(); // 执行目标方法 long endTime = System.currentTimeMillis(); System.out.println("方法 " + joinPoint.getSignature().getName() + " 执行耗时: " + (endTime - startTime) + "ms"); return result; } } ``` 注意事项: 1. 确保Spring Boot项目已经添加了AOP依赖(spring-boot-starter-aop)。 2. 自定义注解的路径在切面类中要写正确。 3. 切面类需要被Spring管理(使用@Component),同时使用@Aspect注解。 通过以上步骤,我们就实现了在Spring Boot中使用AOP和自定义注解来增强方法的功能。 参考引用: [1]中提到了自定义注解AOP的使用,我们参考了其注解定义和切面类结构[^1]。 [3]中提到了切面类的基本结构[^3]。</think>### Spring Boot 通过 AOP 实现自定义注解功能 在 Spring Boot 中使用 AOP 实现自定义注解功能需要以下步骤,参考了站内多个实现案例[^1][^3][^4]: --- #### 1. 添加 AOP 依赖 ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> ``` --- #### 2. 创建自定义注解 定义注解类并指定作用范围: ```java import java.lang.annotation.*; @Target(ElementType.METHOD) // 作用于方法级别 @Retention(RetentionPolicy.RUNTIME) // 运行时保留 @Documented public @interface CustomLog { String value() default ""; // 可配置的注解属性 LogType type() default LogType.OPERATION; // 枚举类型属性 } ``` 定义日志类型枚举: ```java public enum LogType { OPERATION, SECURITY, PERFORMANCE } ``` --- #### 3. 创建切面类 实现注解的横切逻辑: ```java import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Aspect @Component public class LogAspect { // 定义切入点:所有被@CustomLog注解的方法 @Pointcut("@annotation(com.example.annotation.CustomLog)") public void logPointcut() {} // 环绕通知(最常用) @Around("logPointcut()") public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { // 1. 获取方法信息 MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); CustomLog annotation = method.getAnnotation(CustomLog.class); // 2. 前置处理(记录开始时间等) long startTime = System.currentTimeMillis(); System.out.println("【" + annotation.type() + "日志】方法开始: " + method.getName()); try { // 3. 执行目标方法 Object result = joinPoint.proceed(); // 4. 后置处理(记录结果) long duration = System.currentTimeMillis() - startTime; System.out.println("方法执行完成,耗时: " + duration + "ms"); return result; } catch (Exception e) { // 5. 异常处理 System.err.println("方法执行异常: " + e.getMessage()); throw e; } } // 前置通知(可选) @Before("logPointcut()") public void logBefore(JoinPoint joinPoint) { // 记录方法参数等 Object[] args = joinPoint.getArgs(); System.out.println("方法参数: " + Arrays.toString(args)); } } ``` --- #### 4. 使用自定义注解 在业务方法上添加注解: ```java @Service public class UserService { @CustomLog(type = LogType.OPERATION, value = "用户查询") public User getUserById(Long id) { // 业务逻辑 } @CustomLog(type = LogType.SECURITY) public void updatePassword(String newPassword) { // 密码更新逻辑 } } ``` --- #### 5. 高级功能实现 ##### 敏感字段加解密(参考[^2]) ```java @Aspect @Component public class EncryptionAspect { @Around("@annotation(SensitiveData)") public Object encryptData(ProceedingJoinPoint pjp) throws Throwable { Object result = pjp.proceed(); if (result instanceof User) { User user = (User) result; user.setPassword(encrypt(user.getPassword())); // 加密处理 } return result; } } ``` ##### 操作日志入库(参考[^4]) ```java @Around("@annotation(OperateLog)") public Object recordOperation(ProceedingJoinPoint pjp) throws Throwable { OperateLog logAnnotation = getAnnotation(pjp); LogEntity log = new LogEntity(); log.setOperationType(logAnnotation.value()); try { Object result = pjp.proceed(); log.setStatus("SUCCESS"); return result; } catch (Exception e) { log.setStatus("FAILED"); throw e; } finally { logService.save(log); // 保存到数据库 } } ``` --- #### 关键注意事项 1. **注解属性访问**:通过 `method.getAnnotation()` 获取注解配置 2. **方法信息获取**: ```java MethodSignature signature = (MethodSignature) joinPoint.getSignature(); String methodName = signature.getName(); Class<?> targetClass = joinPoint.getTarget().getClass(); ``` 3. **代理限制**:AOP 对 private 方法和内部调用无效 4. **执行顺序**:使用 `@Order` 注解控制多个切面的执行顺序 5. **性能优化**:避免在切面中执行耗时操作,特别是高频方法 > 完整实现可参考 Spring Boot 操作日志管理[^4] 和敏感字段加解密[^2] 的案例实现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Run_the_ant

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值