JDK动态代理和CGLIB动态代理

下面是对 JDK动态代理CGLIB动态代理 的详细解释、关键区别,以及它们在 Spring框架 中的应用。


一、JDK动态代理(JDK Dynamic Proxy)

1. 原理

JDK动态代理是Java标准库(java.lang.reflect.Proxy)提供的机制,它通过接口来实现代理。其核心是:

  • 在运行时,动态生成一个实现了指定接口的代理类。
  • 该代理类继承自 java.lang.reflect.Proxy,并实现你指定的接口列表。
  • 所有接口方法的调用都会被转发到 InvocationHandlerinvoke() 方法中。

2. 实现步骤

// 1. 定义接口
public interface UserService {
    void save();
    void delete();
}

// 2. 实现类
public class UserServiceImpl implements UserService {
    public void save() { System.out.println("保存用户"); }
    public void delete() { System.out.println("删除用户"); }
}

// 3. 创建InvocationHandler
public class MyInvocationHandler implements InvocationHandler {
    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("前置增强:在方法执行前");
        Object result = method.invoke(target, args); // 调用原始方法
        System.out.println("后置增强:在方法执行后");
        return result;
    }
}

// 4. 创建代理对象
UserService proxy = (UserService) Proxy.newProxyInstance(
    UserService.class.getClassLoader(),
    new Class[]{UserService.class},
    new MyInvocationHandler(new UserServiceImpl())
);

proxy.save(); // 输出增强日志

3. 优点

  • 纯Java标准库,无需额外依赖。
  • 类型安全,编译期检查接口方法。
  • 性能较好,适用于接口代理场景。

4. 缺点

  • 必须基于接口:目标类必须实现至少一个接口,否则无法使用。
  • 无法代理没有接口的类

二、CGLIB动态代理(Code Generation Library)

1. 原理

CGLIB(Code Generation Library)是一个第三方字节码操作库(基于ASM),它通过继承目标类来实现代理。

  • 在运行时,动态生成目标类的子类
  • 重写目标类中的非final方法,插入增强逻辑(如事务、日志)。
  • 使用MethodInterceptor拦截方法调用。

2. 实现步骤

// 1. 目标类(不需要实现接口)
public class UserService {
    public void save() { System.out.println("保存用户"); }
    public void delete() { System.out.println("删除用户"); }
}

// 2. 实现MethodInterceptor
public class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("前置增强:CGLIB代理");
        Object result = proxy.invokeSuper(obj, args); // 调用父类(原类)方法
        System.out.println("后置增强:CGLIB代理");
        return result;
    }
}

// 3. 创建代理
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class); // 设置父类
enhancer.setCallback(new MyMethodInterceptor());

UserService proxy = (UserService) enhancer.create();
proxy.save(); // 输出增强日志

⚠️ 注意:CGLIB 不能代理 final 类或 final 方法,因为无法生成子类覆盖。

3. 优点

  • 无需接口:可以代理任意非final类。
  • 功能强大,支持类级别的代理。
  • Spring中默认用于没有接口的类

4. 缺点

  • 依赖第三方库(cglib-nodep 或 asm)。
  • 性能略低于JDK代理(字节码生成和类加载有开销)。
  • 无法代理 final 类/方法。
  • 生成的子类可能影响继承关系(如 getClass() 返回的是代理类,不是原始类)。

三、JDK动态代理 vs CGLIB动态代理:关键区别对比

特性 JDK动态代理 CGLIB动态代理
代理依据 接口(Interface) 类(Class)
代理方式 实现接口,组合调用 继承目标类,重写方法
是否需要接口 ✅ 必须 ❌ 不需要
代理目标限制 不能代理类,只能代理接口 不能代理 final 类或 final 方法
性能 较高(反射调用,但JVM优化好) 略低(字节码生成+方法拦截)
依赖 Java标准库(无依赖) 需要引入 CGLIB / ASM 库
代理类生成方式 Proxy.newProxyInstance() Enhancer.create()
方法调用方式 Method.invoke() MethodProxy.invokeSuper()
适用场景 接口明确、标准Java开发 无接口的第三方类、Spring AOP默认备选

总结一句话
JDK代理是“面向接口”,CGLIB是“面向类”


四、在Spring中的应用

Spring AOP(面向切面编程)底层就使用了这两种代理机制,自动选择策略如下:

1. Spring AOP代理选择规则(Spring 5+)

目标对象情况 Spring 选择的代理方式
实现了至少一个接口 JDK动态代理(优先)
没有实现任何接口 CGLIB动态代理
配置了 proxyTargetClass = true ✅ 强制使用 CGLIB(即使有接口)

2. 如何配置代理方式?

✅ 方式一:通过注解配置(推荐)

@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true) // 强制使用CGLIB
public class AppConfig {
}

✅ 方式二:XML配置

<aop:aspectj-autoproxy proxy-target-class="true"/>

✅ 方式三:@Aspect 切面类中使用

@Aspect
@Component
public class LoggingAspect {
    @Around("execution(* com.example.service.*.*(..))")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        // 增强逻辑
    }
}

Spring默认行为

  • 有接口 → JDK代理
  • 无接口 → CGLIB代理
    这种“智能选择”机制极大提升了灵活性。

3. 为什么Spring优先选择JDK代理?

  • 更轻量,无额外依赖。
  • 更符合“面向接口编程”的设计哲学。
  • 在Spring中,大多数Bean(如Service、DAO)都实现了接口。

4. 什么时候必须用CGLIB?

  • 你写的类没有接口(如工具类、第三方类)。
  • 你想代理 @Controller@Service 等注解类(这些类通常不显式写接口)。
  • 你想使用 @EnableAspectJAutoProxy(proxyTargetClass = true) 统一使用CGLIB,避免混合代理导致的类型不一致问题(如 instanceof 判断)。

5. 潜在陷阱:代理对象类型问题

@Service
public class UserService { ... } // 没有接口

@Autowired
private UserService userService; // 正常注入

// 如果用JDK代理,这里会报错!因为JDK代理只能代理接口,而UserService没有接口 → Spring自动用CGLIB

但如果强制用JDK代理(比如配置错误),会抛出:

No qualifying bean of type 'UserService' available: expected at least 1 bean which qualifies as autowire candidate

因为Spring创建的是 Proxy 类型,而你试图注入的是 UserService 类型,类型不匹配。

✅ 所以:没有接口的类,Spring必须用CGLIB,否则无法注入。


五、补充:Spring 5+ 和 Spring Boot 的默认行为

  • Spring Boot 2.x+ 默认使用 CGLIB 作为AOP代理机制(从 Spring 5 开始,CGLIB性能已大幅提升,且兼容性更好)。
  • 实际上,Spring Boot 默认 proxyTargetClass=false,所以还是优先JDK代理。
  • 但绝大多数Spring Boot应用中,@Service@Controller 都没有接口,所以实际运行中大部分是CGLIB代理

你可以通过日志查看Spring使用了哪种代理:

logging.level.org.springframework.aop=DEBUG

输出示例:

Creating CGLIB proxy for bean 'userService'

六、总结:如何选择?

场景 推荐代理方式
有接口,追求标准、轻量 ✅ JDK动态代理
无接口,或代理第三方类 ✅ CGLIB动态代理
希望统一代理风格(避免混合) ✅ 设置 proxyTargetClass=true 强制CGLIB
高性能、高并发系统 ✅ 两者性能差异极小,可忽略,优先CGLIB简化配置
避免代理 final 类或方法 ❌ 两者都不能代理 final 方法

七、扩展:为什么Spring AOP不能代理 private / static / final 方法?

  • JDK代理:只代理接口方法 → 无法代理私有方法。
  • CGLIB代理:通过继承重写方法 → private 方法不能被子类访问,static 方法不能被重写,final 方法不能被重写。
  • 所以:Spring AOP只能代理 public 方法(无论哪种代理方式)。

💡 提示:如果你的切面没生效,先检查方法是否为 public


✅ 总结一句话

JDK动态代理基于接口,轻量标准;CGLIB基于继承,强大灵活。Spring AOP根据目标类是否有接口自动选择,优先JDK,无接口则用CGLIB,二者共同支撑了Spring强大的AOP能力。

理解这两种代理机制,是掌握Spring AOP、事务管理、日志切面等核心功能的基石。

posted @ 2025-11-17 13:17  悠哉大斌  阅读(27)  评论(0)    收藏  举报