springboot aop无效的情况

本文探讨了在Spring框架中,如何通过AOP(面向切面编程)和动态代理技术实现对业务方法的拦截和增强。重点分析了JDK动态代理和CGLIB在实现过程中的差异,以及在具体场景下如何正确使用AopContext.currentProxy()来确保切面能够生效。

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

项目结构
在这里插入图片描述

package com.example.demo.inter;

public interface CustomerService {
     void doSomething1();
     void doSomething2();
}
package com.example.demo.inter;

import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;

@Service
public class CustomerServiceImpl implements CustomerService {

    @Override
    public void doSomething1() {
        System.out.println("CustomerServiceImpl.doSomething1()");
        doSomething2();
        ((CustomerService) AopContext.currentProxy()).doSomething2();
    }

    @Override
    public void doSomething2() {
        System.out.println("CustomerServiceImpl.doSomething2()");
    }

}
package com.example.demo;


import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class CustomerServiceInterceptor {

    @Before("execution(* com.example.demo.inter..*.*(..))")
    public void doBefore() {
        System.out.println("do some important things before...");
    }
}
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@EnableAspectJAutoProxy(proxyTargetClass=true, exposeProxy=true)
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}
package com.example.demo;

import com.example.demo.inter.CustomerService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class DemoApplicationTests {

    @Autowired
    CustomerService customerService;

    @Test
    public void testAOP() {
        customerService.doSomething1();
    }

    @Test
    void contextLoads() {
    }

}

运行下testAOP,为啥doSomething2()没有切面效果,使用AopContext.currentProxy就可以了?

拦截器的实现原理就是动态代理,实现AOP机制。Spring 的代理实现有两种:一是基于 JDK Dynamic Proxy 技术而实现的;二是基于 CGLIB 技术而实现的。如果目标对象实现了接口,在默认情况下Spring会采用JDK的动态代理实现AOP,CustomerServerImpl正是这种情况。

JDK动态代理生成的CustomerServiceImpl的代理类翻译过来如下:

package com.example.demo;

import com.example.demo.inter.CustomerService;


public class CustomerServiceProxy implements CustomerService {

    private CustomerService customerService;

    public void setCustomerService(CustomerService customerService) {
        this.customerService = customerService;
    }

    public void doSomething1() {
        doBefore();
        customerService.doSomething1();
        // 默认,所以不会执行doBefore
        customerService.doSomething2();
        // 加入 AopContext.currentProxy的效果,完成切面效果
        this.doSomething2();
    }

    public void doSomething2() {
        doBefore();
        customerService.doSomething2();
    }

    private void doBefore() {
        System.out.println("do some important things before...");
    }
}

这样很直观地明白为啥要使用AopContext.currentProxy了。

<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
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值