Spring AOP实现原理(动态代理 vs CGLib)

Spring AOP实现原理(动态代理 vs CGLib)

1. 是什么?

Spring AOP(面向切面编程) 是一种编程思想,允许你在不修改原有代码的情况下,动态地给某些方法添加额外功能(如日志、事务、权限控制等)。

动态代理CGLib 是 Spring AOP 的两种底层实现方式:

  • JDK 动态代理:基于接口,运行时生成代理类(Proxy.newProxyInstance())。
  • CGLib 代理:基于继承,运行时生成目标类的子类(Enhancer.create())。

举例

  • 你有一个 UserService 类,里面有个 saveUser() 方法。
  • 你想在 saveUser() 执行前后自动打印日志,但不想改 UserService 的代码。
  • Spring AOP 会动态生成一个代理类,在调用 saveUser() 时,先执行日志逻辑,再调用真实方法。

2. 为什么要用?

(1)解决代码重复问题

  • 比如多个方法都需要记录日志、事务管理、权限校验,如果每个方法都写一遍,代码会变得臃肿。
  • AOP 可以统一处理这些横切关注点,让业务代码更干净。

(2)解耦

  • 业务代码(如 UserService.saveUser())和增强逻辑(如日志、事务)分离。
  • 修改增强逻辑时,不影响业务代码。

(3)灵活扩展

  • 可以随时添加或移除功能(如:开发阶段加日志,生产环境去掉)。

3. 怎么学?

(1)先理解 AOP 核心概念

概念解释示例
切面(Aspect)封装增强逻辑的类@Aspect 注解的类
通知(Advice)增强的具体逻辑@Before(方法前执行)、@After(方法后执行)
切点(Pointcut)定义哪些方法需要增强@Pointcut("execution(* com.example.service.*.*(..))")
连接点(JoinPoint)目标方法(如 saveUser()可以在通知里获取方法名、参数等
一、三大核心概念关系图
Aspect切面
Advice通知
Pointcut切点
具体增强逻辑
方法匹配规则
二、切点(Pointcut)详解

1. 核心作用
精确定位需要增强的方法,如:“所有Service层的public方法”

2. 表达式语法

// 常用表达式示例
@Pointcut("execution(public * com.example.service.*.*(..))") // 匹配service包下所有public方法
@Pointcut("@annotation(com.example.annotation.Log)") // 匹配带@Log注解的方法
@Pointcut("within(@org.springframework.stereotype.Service *)") // 匹配Service注解的类

3. 组合切点

@Pointcut("execution(* com.example..*(..)) && args(String)")
public void stringParams() {}

@Pointcut("execution(* save*(..)) || execution(* update*(..))")
public void writeOperations() {}
三、通知(Advice)类型
通知类型注解执行时机典型应用场景
前置通知@Before方法执行前权限校验、参数校验
后置通知@After方法执行后(无论是否异常)资源清理、日志记录
返回通知@AfterReturning方法正常返回后结果处理、成功日志
异常通知@AfterThrowing方法抛出异常时异常处理、错误监控
环绕通知@Around方法执行前后(完全控制流程)性能监控、事务管理

环绕通知示例

@Around("serviceMethods()")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
    long start = System.currentTimeMillis();
    try {
        Object result = pjp.proceed(); // 控制目标方法执行
        log.info("执行成功, 耗时: {}ms", System.currentTimeMillis()-start);
        return result;
    } catch (Exception e) {
        log.error("执行异常", e);
        throw e;
    }
}
四、切面(Aspect)实现
@Aspect
@Component
@Slf4j
public class BusinessMonitorAspect {
    
    // 定义切点(可复用)
    @Pointcut("execution(* com.example..service.*.*(..))")
    public void serviceLayer() {}
    
    // 前置通知
    @Before("serviceLayer()")
    public void logMethodStart(JoinPoint jp) {
        log.debug("开始执行: {}", jp.getSignature());
    }
    
    // 返回通知(可获取返回值)
    @AfterReturning(
        pointcut = "serviceLayer()",
        returning = "result")
    public void logSuccess(JoinPoint jp, Object result) {
        log.info("执行成功, 返回: {}", result);
    }
    
    // 异常通知(可捕获特定异常)
    @AfterThrowing(
        pointcut = "serviceLayer()",
        throwing = "ex")
    public void logFailure(JoinPoint jp, NullPointerException ex) {
        log.error("空指针异常", ex);
    }
}
五、Spring AOP执行顺序

完整调用链

ClientProxyAround AdviceBefore AdviceTarget MethodAfterReturning/AfterThrowingAfter Advice方法调用前置处理执行返回proceed()返回结果根据结果分支返回最终执行返回返回增强结果最终响应ClientProxyAround AdviceBefore AdviceTarget MethodAfterReturning/AfterThrowingAfter Advice

顺序控制注解

@Aspect
@Order(1) // 数字越小优先级越高
public class HighPriorityAspect {
    // 会先于@Order(2)的切面执行
}
六、性能优化方法
  1. 切点表达式优化

    // 低效写法(每次执行都解析)
    @Around("execution(* com.example..*(..)) && args(param)")
    
    // 高效写法(编译期优化)
    @Pointcut("execution(* com.example..*(..))")
    public void generalPointcut() {}
    
    @Around("generalPointcut() && args(param)")
    
  2. 避免在切面中做IO操作

    // 错误示范
    @Around("serviceMethods()")
    public Object badPractice(ProceedingJoinPoint pjp) throws Throwable {
        // 同步写入日志文件会严重影响性能!
        Files.write(Paths.get("log.txt"), "调用记录".getBytes());
        return pjp.proceed();
    }
    
  3. 使用条件切面

    @Around("serviceMethods()")
    public Object conditionalAdvice(ProceedingJoinPoint pjp) throws Throwable {
        if (log.isDebugEnabled()) { // 根据日志级别控制
            long start = System.nanoTime();
            Object result = pjp.proceed();
            log.debug("执行耗时: {}ns", System.nanoTime()-start);
            return result;
        }
        return pjp.proceed();
    }
    

(2)动手实现

  • JDK 动态代理(适合接口):
public class JdkProxyDemo {
    public static void main(String[] args) {
        UserService target = new UserServiceImpl();
        UserService proxy = (UserService) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            (p, method, args1) -> {
                System.out.println("Before: " + method.getName());
                Object result = method.invoke(target, args1);
                System.out.println("After: " + method.getName());
                return result;
            });
        proxy.saveUser(); // 调用代理方法
    }
}

CGLib 代理(适合类):

public class CglibProxyDemo {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserServiceImpl.class);
        enhancer.setCallback((MethodInterceptor) (obj, method, args1, proxy) -> {
            System.out.println("Before: " + method.getName());
            Object result = proxy.invokeSuper(obj, args1);
            System.out.println("After: " + method.getName());
            return result;
        });
        UserService proxy = (UserService) enhancer.create();
        proxy.saveUser(); // 调用代理方法
    }
}

(3)学习 Spring AOP 实战

@Aspect
@Component
public class LogAspect {
    @Before("execution(* com.example.service.*.*(..))") // 切点表达式
    public void beforeLog(JoinPoint joinPoint) {
        System.out.println("方法执行前: " + joinPoint.getSignature().getName());
    }
}

4. 怎么使用?

(1)Spring Boot 启用 AOP

@SpringBootApplication
@EnableAspectJAutoProxy // 开启 AOP
public class MyApp {
    public static void main(String[] args) {
        SpringApplication.run(MyApp.class, args);
    }
}

(2)定义切面

@Aspect
@Component
public class TransactionAspect {
    @Around("@annotation(org.springframework.transaction.annotation.Transactional)")
    public Object manageTransaction(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("开启事务");
        try {
            Object result = pjp.proceed(); // 执行目标方法
            System.out.println("提交事务");
            return result;
        } catch (Exception e) {
            System.out.println("回滚事务");
            throw e;
        }
    }
}

(3)测试

@Service
public class UserService {
    @Transactional // 会被 AOP 代理增强
    public void saveUser() {
        System.out.println("保存用户...");
    }
}

运行结果

开启事务
保存用户...
提交事务

5. JDK动态代理 vs CGLib 深度对比

(1)核心区别

对比维度JDK动态代理CGLib
实现原理基于接口(反射调用)基于子类继承(字节码增强)
代理目标必须实现至少一个接口可以代理普通类(final类/方法除外)
生成方式Proxy.newProxyInstance()Enhancer.create()
性能特点调用速度快(约快15%-20%)生成速度快(首次加载后缓存字节码)
内存占用较小(仅生成接口代理)较大(生成子类包含所有方法)
Spring默认选择有接口时优先使用proxyTargetClass=true时强制使用

(2)性能实测数据(百万次调用)

// 测试代码片段
public class ProxyBenchmark {
    public static void main(String[] args) {
        // JDK代理测试
        Interface jdkProxy = createJdkProxy();
        long jdkStart = System.currentTimeMillis();
        for (int i = 0; i < 1_000_000; i++) {
            jdkProxy.doSomething();
        }
        System.out.println("JDK 耗时: " + (System.currentTimeMillis() - jdkStart) + "ms");

        // CGLib代理测试
        TargetClass cglibProxy = createCglibProxy();
        long cglibStart = System.currentTimeMillis();
        for (int i = 0; i < 1_000_000; i++) {
            cglibProxy.doSomething();
        }
        System.out.println("CGLib 耗时: " + (System.currentTimeMillis() - cglibStart) + "ms");
    }
}

典型测试结果

  • JDK代理调用:约120-150ms
  • CGLib代理调用:约140-180ms
  • 直接调用(无代理):约80-100ms

(3)底层实现原理

1. JDK动态代理核心流程

// 生成的代理类伪代码
public final class $Proxy0 extends Proxy implements TargetInterface {
    public $Proxy0(InvocationHandler h) {
        super(h);
    }

    public void doSomething() {
        // 通过InvocationHandler转发调用
        super.h.invoke(this, 
            TargetInterface.class.getMethod("doSomething"), 
            null);
    }
}

2. CGLib代理核心流程

// 生成的子类伪代码
public class TargetClass$$EnhancerByCGLIB extends TargetClass {
    private MethodInterceptor interceptor;

    public void doSomething() {
        // 通过MethodInterceptor拦截
        interceptor.intercept(this, 
            getClass().getMethod("doSomething"), 
            new Object[0], 
            MethodProxy.create(...));
    }
}

3.Spring的选择策略

开始创建代理
proxyTargetClass=true?
强制使用CGLib
目标类有接口?
JDK动态代理

(4)生产环境选型建议

优先使用JDK动态代理

  • 服务间通过接口调用(RPC/Feign)
  • 需要代理的类已实现接口
  • 对内存敏感的场景

优先使用CGLib

  • 需要代理具体类(如第三方库)
  • 代理final方法外的所有方法
  • 项目已大量使用类代理

5. 组成部分

组成部分作用示例
JDK 动态代理基于接口生成代理Proxy.newProxyInstance()
CGLib 代理基于子类生成代理Enhancer.create()
@Aspect定义切面@Aspect public class LogAspect
@Before/@After定义通知@Before("execution(* com.example..*(..))")
@Pointcut定义切点@Pointcut("within(@org.springframework.stereotype.Service *)")
@EnableAspectJAutoProxy启用 AOP@EnableAspectJAutoProxy(proxyTargetClass=true)

6.手动实现JDK动态代理(InvocationHandler接口)

(1)JDK 动态代理核心机制

  1. 依赖接口:必须通过接口创建代理
  2. 核心类
    • java.lang.reflect.Proxy:生成代理类
    • java.lang.reflect.InvocationHandler:定义代理逻辑
  3. 执行流程
客户端代理对象InvocationHandler真实对象调用方法触发invoke()反射调用方法返回结果返回增强后的结果最终结果客户端代理对象InvocationHandler真实对象

(2)完整实现代码

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class JdkProxyDemo {

    // 1. 定义业务接口
    interface UserService {
        void saveUser(String username);
        String getUserId(int id);
    }

    // 2. 实现真实对象
    static class UserServiceImpl implements UserService {
        @Override
        public void saveUser(String username) {
            System.out.println("真实保存用户: " + username);
        }

        @Override
        public String getUserId(int id) {
            return "用户ID-" + id;
        }
    }

    // 3. 实现调用处理器
    static class MyInvocationHandler implements InvocationHandler {
        private final Object target; // 真实对象

        public MyInvocationHandler(Object target) {
            this.target = target;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 前置增强
            System.out.println("【代理】准备执行: " + method.getName());
            
            // 反射调用真实方法
            Object result = method.invoke(target, args);
            
            // 后置增强
            System.out.println("【代理】执行完成: " + method.getName());
            return result;
        }
    }

    public static void main(String[] args) {
        // 4. 创建真实对象
        UserService realService = new UserServiceImpl();
        
        // 5. 创建代理对象
        UserService proxyInstance = (UserService) Proxy.newProxyInstance(
            realService.getClass().getClassLoader(), // 类加载器
            realService.getClass().getInterfaces(),  // 接口数组
            new MyInvocationHandler(realService)     // 调用处理器
        );
        
        // 6. 测试代理调用
        proxyInstance.saveUser("张三"); 
        System.out.println(proxyInstance.getUserId(1001));
    }
}

(3)关键代码逐行解析

1. 创建代理对象(核心方法)

Proxy.newProxyInstance(
    ClassLoader loader,          // 1. 使用真实对象的类加载器
    Class<?>[] interfaces,      // 2. 代理要实现的接口
    InvocationHandler h         // 3. 自定义的调用处理器
)

2. InvocationHandler 接口方法

public Object invoke(
    Object proxy,   // 代理对象本身(一般不用)
    Method method,  // 当前调用的方法对象
    Object[] args   // 方法参数
) throws Throwable {
    // 在这里编写增强逻辑
}

3. 反射调用真实方法

method.invoke(target, args);
// target: 真实对象
// args: 方法参数

(4)执行结果分析

【代理】准备执行: saveUser
真实保存用户: 张三
【代理】执行完成: saveUser

【代理】准备执行: getUserId
【代理】执行完成: getUserId
用户ID-1001

总结

  1. 是什么?
    • AOP 是一种编程思想,动态代理和 CGLib 是实现方式。
  2. 为什么要用
    • 解决代码重复、解耦、灵活扩展。
  3. 怎么学
    • 理解核心概念(切面、通知、切点),动手写代理。
  4. 怎么用
    • @EnableAspectJAutoProxy + @Aspect + @Before/@After
  5. 组成部分
    • JDK 动态代理(接口)、CGLib(类)、@Aspect@Pointcut

适用场景

  • 日志记录
  • 事务管理(@Transactional
  • 权限校验
  • 性能监控(统计方法耗时)

一句话理解 AOP
“在不改代码的情况下,动态给方法加功能” 🚀

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值