探索Java动态代理的奥秘:JDK vs CGLIB

简介: 动态代理是一种在 运行时动态生成代理类的技术,无需手动编写代理类代码。它通过拦截目标方法的调用,实现对核心逻辑的 无侵入式增强(如日志、事务、权限控制等)。

一、关于动态代理

1.1 简介

动态代理是一种在 运行时动态生成代理类 的技术,无需手动编写代理类代码。它通过拦截目标方法的调用,实现对核心逻辑的 无侵入式增强(如日志、事务、权限控制等)。

image​

‍

1.2 发展

  1. 早期概念与雏形
  • 1995年 - Java诞生:Java最初发布时,并没有直接支持动态代理的功能。然而,随着面向对象编程(OOP)理念的普及,开发者开始寻求更灵活的方式来处理代码复用和模块化问题。
  • 1998年 - JDK 1.2引入反射API:虽然JDK 1.2并未直接提供动态代理的支持,但它引入了反射API (java.lang.reflect​),这为后续动态代理的实现奠定了基础。反射允许程序在运行时检查或"反射"自身,并操作内部属性和方法。
  1. 正式引入动态代理(JDK 1.3)
  • 2000年 - JDK 1.3:这是Java动态代理的一个重要里程碑。JDK 1.3正式引入了java.lang.reflect.Proxy​类和java.lang.reflect.InvocationHandler​接口,提供了对动态代理的支持。开发者可以通过这些API在运行时创建实现了指定接口的代理对象。

    • Proxy类:用于创建动态代理实例。
    • InvocationHandler接口:所有方法调用都将通过此接口传递给代理对象。

    这一版本使得Java能够在运行时生成代理类,而无需事先定义具体的代理类,极大地增强了开发的灵活性。

  1. 增强与优化(JDK 1.4及之后)
  • 2002年 - JDK 1.4:尽管JDK 1.4并没有对动态代理本身做出重大改进,但这一时期Java整体性能的提升以及对反射机制的进一步优化,间接促进了动态代理的应用与发展。
  • 2004年 - JDK 5 (1.5) :引入了注解(Annotations)、泛型(Generics)等新特性,这些特性虽然不是专门为动态代理设计的,但它们扩展了动态代理的应用场景。例如,利用注解可以更方便地配置代理行为。
  • 2006年 - JDK 6 (1.6) :继续对反射机制进行优化,提升了动态代理的性能。同时,随着Spring框架等流行框架的广泛使用,动态代理技术得到了更广泛的实践和验证。
  1. 现代应用与扩展
  • CGLIB库:由于动态代理只能代理实现了接口的类,对于没有实现接口的类,则需要使用其他技术,如CGLIB(Code Generation Library)。CGLIB通过生成目标类的子类来实现代理功能,适用于那些未实现接口的情况。
  • AspectJ与AOP:随着面向切面编程(AOP)理念的兴起,动态代理成为实现AOP的关键技术之一。AspectJ是一个流行的AOP框架,它利用动态代理实现了诸如日志记录、事务管理等功能。
  • 现代框架中的应用:许多现代Java框架,如Spring和Hibernate,都广泛使用了动态代理技术来实现AOP、依赖注入等功能。这些框架通过动态代理简化了复杂系统的开发,提高了代码的可维护性和灵活性。

‍

Spring AOP 示例:

@Aspect
@Component
public class LogAspect {
   
    @Before("execution(* com.example.service.*.*(..))")
    public void before(JoinPoint joinPoint) {
   
        System.out.println("前置增强: " + joinPoint.getSignature().getName());
    }
}

‍

阶段 技术 特点 局限性
静态代理时代 手动编写代理类 简单直接 代码冗余,灵活性差
JDK 动态代理时代 基于接口的动态代理 运行时生成代理类,减少代码冗余 只能代理接口,反射性能较低
CGLIB 动态代理时代 基于继承的动态代理 支持代理普通类,性能更优 无法代理 final 类/方法,需第三方库
Spring 时代 动态代理的全面应用 结合 AOP,支持灵活配置 依赖 Spring 框架

动态代理技术的演进,体现了 Java 生态对 代码复用 和 灵活性 的不懈追求。未来,随着技术的进一步发展,动态代理将在更多场景中发挥重要作用。

‍

1.3 特点

优点 缺点
无需手动编写代理类,减少代码冗余 只能代理接口,无法代理类
支持统一处理多个方法的增强逻辑 反射调用性能略低于直接调用
更灵活,适合AOP等横切关注点的实现

‍

1.4 实现方式

Java 提供了两种动态代理方式:

  • JDK 动态代理:基于接口实现(Java 原生支持)
  • CGLIB 动态代理:基于继承实现(需第三方库)

‍

JDK 动态代理与 CGLIB 动态代理的对比

特性 JDK 动态代理 CGLIB 动态代理
代理类型 只能代理实现了接口的类 可以代理普通类(没有接口)
实现方式 使用反射生成代理对象,委托给 InvocationHandler​ 处理方法调用 通过继承目标类生成代理类,重写目标类的方法
性能 因为基于反射,性能较低 性能较好,但会生成字节码
使用限制 只能对接口进行代理 对目标类有继承限制,不能代理 final​ 类及 final​ 方法
框架支持 适用于 Spring AOP、JDBC 动态代理等 Spring AOP 在目标类没有接口时使用 CGLIB

‍

1.5 应用场景

动态代理的应用场景非常广泛,以下是一些常见的用途:

  1. 日志记录:在方法执行前后记录日志。
  2. 事务管理:在方法执行前启动事务,执行后提交事务。
  3. 权限控制:在执行目标方法前进行权限校验。
  4. 延迟加载:代理可以延迟初始化目标对象,直到需要使用时才加载。
  5. 性能监控:监控方法执行的时间或次数。

‍

‍

二、JDK动态代理

2.1 简介

JDK动态代理是Java提供的一种在运行时创建代理对象的机制,它允许开发者在不修改原始类的情况下,为接口实现类添加额外的功能(如日志记录、权限检查等)。

这种机制基于java.lang.reflect.Proxy​类和java.lang.reflect.InvocationHandler​接口。

image​

‍

2.2 特点

  1. 灵活性高:可以在运行时动态创建代理对象,无需为每个具体类编写代理类。
  2. 减少代码量:避免了静态代理中为每个具体类编写代理类的繁琐工作。
  3. 增强功能:可以轻松地为多个不同接口的对象添加统一的横切关注点(如日志记录、事务管理等)。
  4. 仅限接口代理:JDK动态代理只能代理实现了接口的类。如果需要代理没有实现接口的类,则需要使用其他技术,如CGLIB库。

‍

2.3 核心概念

JDK的动态代理需要了解两个类

  1. Proxy类:位于java.lang.reflect​包中,提供了用于创建动态代理类和实例的方法。

    Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)​ 方法用于创建代理实例。

  2. InvocationHandler接口:所有方法调用都将通过此接口传递给代理对象。该接口包含一个invoke​方法,代理对象上的任何方法调用都会触发此方法。

    invoke()​ 方法接受三个参数:

  • proxy​:代理对象。
  • method​:被调用的方法。
  • args​:方法的参数。

‍

Proxy ( 代理)

image​

‍

InvocationHandler(调用处理程序)

image​

‍

2.4 工作原理

JDK 动态代理通过反射机制生成代理类,代理类实现目标接口并将方法调用转发到 InvocationHandler​ 实现类。具体的工作原理如下:

  1. 创建代理类:Proxy.newProxyInstance()​ 方法会创建一个动态代理类。这个代理类实现了指定的接口,并将所有的方法调用委派给 InvocationHandler​ 接口的 invoke()​ 方法。
  2. 方法拦截:当代理类的方法被调用时,JDK 会通过反射机制调用 InvocationHandler​ 的 invoke()​ 方法。
  3. 调用目标方法:在 invoke()​ 方法中,我们可以加入自定义的逻辑(例如日志记录、权限控制等),然后使用反射调用目标对象的方法。

‍

2.5 实现步骤

以下是使用JDK动态代理的基本步骤:

  1. 定义业务接口:首先定义一个或多个业务接口。
  2. 实现业务逻辑的具体类:编写具体的业务逻辑实现类。
  3. 实现InvocationHandler接口:编写一个类实现InvocationHandler​接口,在invoke​方法中定义代理行为。
  4. 创建代理实例:使用Proxy.newProxyInstance​方法创建代理对象。

‍

2.6 实现示例

示例1—日志功能

以下是一个完整的示例,展示了如何使用JDK动态代理为一个简单的业务接口添加日志记录功能。

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

// 1. 定义业务接口
interface UserService {
   
    void addUser(String name);
}

// 2. 实现业务逻辑的具体类
class UserServiceImpl implements UserService {
   
    @Override
    public void addUser(String name) {
   
        System.out.println("添加用户:" + name);
    }
}

// 3. 实现InvocationHandler接口
class UserServiceInvocationHandler implements InvocationHandler {
   
    private Object target;

    public UserServiceInvocationHandler(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;
    }
}

public class JdkDynamicProxyExample {
   
    public static void main(String[] args) {
   
        // 创建目标对象
        UserService userService = new UserServiceImpl();

        // 创建InvocationHandler
        InvocationHandler handler = new UserServiceInvocationHandler(userService);

        // 使用Proxy.newProxyInstance方法创建代理对象
        UserService proxy = (UserService) Proxy.newProxyInstance(
                userService.getClass().getClassLoader(),
                userService.getClass().getInterfaces(),
                handler);

        // 使用代理对象调用方法
        proxy.addUser("张三");
    }
}

关键方法解析

  • Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) :

    • loader​:指定代理类的类加载器。
    • interfaces​:代理类需要实现的接口列表。
    • h​:实现InvocationHandler​接口的对象,负责处理代理对象的所有方法调用。
  • InvocationHandler.invoke(Object proxy, Method method, Object[] args) :

    • proxy​:代理对象本身。
    • method​:被调用的方法对象。
    • args​:方法调用时传入的实际参数列表。

‍

输出结果:

前置操作:权限检查
添加用户:张三
后置操作:日志记录

‍

image​

‍

‍

三、CGLIB 动态代理

3.1 简介

CGLIB(Code Generation Library)是一个强大的字节码生成库,广泛用于在运行时动态生成类的子类作为代理对象。与 JDK 动态代理不同,CGLIB 可以代理普通类,而不仅仅是接口

‍

3.2 发展

CGLIB(Code Generation Library)是一个强大的字节码生成库,最初的目的是通过动态生成字节码来增强 Java 程序的功能,尤其是实现动态代理。CGLIB 作为一种基于继承的动态代理技术,得到了广泛应用,特别是在 Spring 框架中。它的主要发展历程如下:

  1. CGLIB 的初始版本与诞生

CGLIB 最初由 Rod Johnson(Spring 框架的创始人)在 2000 年代初期为解决 Java 中动态代理的不足而开发。它的设计初衷是为了解决 Java 中基于接口的动态代理(如 JDK 动态代理)存在的局限性——JDK 动态代理只能代理实现了接口的类,而 CGLIB 动态代理不依赖接口,可以代理任何类,甚至没有接口的类。

  1. CGLIB 作为 Spring 的核心组件

在 Spring Framework 早期版本中,CGLIB 被用作 AOP(面向切面编程)的核心实现之一。在 Spring AOP 中,CGLIB 被广泛使用来生成代理对象,从而在方法调用时插入切面逻辑,如事务管理、日志记录等。

特别是在 Spring 中的 代理模式 和 AOP(Aspect-Oriented Programming) 的实现,CGLIB 提供了非常有效的方式来拦截对象方法的调用,并进行增强。Spring 将 CGLIB 与 JDK 动态代理结合使用,具体选择哪种代理方式取决于目标对象是否实现了接口。

  1. CGLIB 发展与更新

CGLIB 项目的版本演化主要体现在性能优化、功能增强、兼容性提升以及 bug 修复等方面。CGLIB 在 2000 年代中期到 2010 年前后获得了较为稳定的版本,但主要的变动集中在:

  • 性能优化:为了提高字节码生成的效率,CGLIB 做了许多优化,例如通过缓存机制避免重复生成相同类的字节码。
  • 增强功能:CGLIB 不仅支持方法拦截,还支持更为复杂的字节码修改,比如动态代理字段、构造函数等。
  • 兼容性提升:随着 Java 语言和 JVM(Java Virtual Machine)的演进,CGLIB 不断修复兼容性问题,使其能够支持新的 Java 特性和版本。
  1. CGLIB 的分支与衍生

随着 Spring 的发展,CGLIB 动态代理的使用逐渐被限制在特定场景中。例如,Spring 在后期引入了 Java 的 JDK 动态代理,并根据对象的特性(是否实现了接口)来选择代理方式。

与此同时,随着 Java 代理技术的发展和对性能要求的提升,CGLIB 的某些特性逐渐被更高效的技术所取代,比如 JDK 动态代理、Java Instrumentation API 等。

尽管如此,CGLIB 仍然在某些场景中(如需要代理没有接口的类)保持着其不可替代的地位。它的设计和思想影响了后来的许多字节码增强技术。

  1. CGLIB 与 Spring 4.x 后期的变化

随着 Spring 4.x 版本的推出,Spring 框架逐渐更多地使用了 JDK 动态代理,并且在 Spring AOP 中开始默认使用基于接口的 JDK 动态代理。当目标对象实现了接口时,Spring 会使用 JDK 动态代理;当目标对象没有实现接口时,Spring 才会退回使用 CGLIB 代理。

Spring 对 CGLIB 的使用有所减少,但 CGLIB 仍然作为 Spring AOP 的重要选择之一存在,尤其是在需要代理的类没有接口时,CGLIB 仍然是最佳选择。

  1. CGLIB 的当前状态与替代技术
  • Java 8 的引入:Java 8 引入了默认方法(default methods​),使得许多使用 CGLIB 的场景能够通过接口的方式解决。Java 8 默认方法让 JDK 动态代理的使用场景更加广泛,从而减少了对 CGLIB 的需求。
  • 字节码操作库的进展:现代的字节码增强工具,如 Byte Buddy、ASM、Javassist,也为字节码增强和代理提供了更为灵活和强大的功能。特别是 Byte Buddy,作为一个轻量级的字节码操作库,提供了比 CGLIB 更丰富的功能,并且在许多场景下成为了替代选择。

尽管如此,CGLIB 仍然在一些需要继承的场景中得到使用,特别是在一些成熟的 Java 项目中,它依旧是代理类生成的核心库之一。

  1. CGLIB 目前的状态

CGLIB 的开发社区已经相对活跃度较低,最新的版本更新较为缓慢。目前,CGLIB 更多地被用作一些旧版项目或需要特定字节码增强的场景。在大多数新项目中,CGLIB 的使用被其他字节码增强库(如 Byte Buddy)取代,尤其是在 Spring 5.x 版本中,Spring 不再默认使用 CGLIB,而是更多地倾向于使用 JDK 动态代理。

‍

3.3 特点

优点

  • 代理普通类:CGLIB 可以代理没有实现接口的普通类,这是它与 JDK 动态代理的主要区别。
  • 性能较高:CGLIB 通过生成子类的方式实现代理,避免了 JDK 动态代理的反射调用,性能更高。
  • 方法拦截灵活:通过实现 MethodInterceptor​ 接口,可以灵活地控制方法调用的行为。

局限性

  • 无法代理 final 类或 final 方法:由于 CGLIB 通过生成子类实现代理,因此无法代理 final 类或 final 方法。
  • 需要引入第三方库:CGLIB 是一个第三方库,需要额外引入依赖。
  • 字节码生成可能影响启动性能:在运行时生成字节码可能会对应用的启动性能产生一定影响。

‍

3.4 工作原理

CGLIB通过在运行时动态地创建目标对象的子类来实现代理。这个过程涉及到字节码的操作,CGLIB依赖于ASM这个开源的Java字节码操作框架来修改类的行为。当使用CGLIB创建代理时,它不会像JDK动态代理那样要求被代理类必须实现一个接口,而是直接对类进行扩展。

  • 字节码生成:CGLIB 通过操作字节码在运行时生成目标类的子类,子类重写了父类的方法,从而实现对方法的拦截和增强。
  • 方法拦截:通过实现 MethodInterceptor​ 接口,可以在方法调用前后插入自定义逻辑。

‍

3.5 核心类

  • Enhancer:用于创建代理对象的主要类。通过配置 Enhancer,可以指定目标类、回调方法等。
  • MethodInterceptor:接口,用于定义方法拦截逻辑。实现该接口的类可以在方法调用前后执行自定义操作。
  • Callback:用于指定代理对象的方法调用行为。MethodInterceptor​ 是 Callback​ 的一种实现。

image​

‍

image​

‍

3.6 实现步骤

  1. 定义业务类:首先需要有一个具体的业务类,该类可能包含一些需要被拦截的方法。
  2. 创建拦截器:实现MethodInterceptor​接口,并重写intercept​方法,在这里可以编写自定义逻辑,比如添加前置处理、后置处理等。
  3. 生成代理类:使用Enhancer​类设置父类为业务类,并指定回调拦截器。然后调用create()​方法生成代理实例。

‍

3.7 实现示例

示例1
// 业务类
public class TargetClass {
   
    public void targetInfo() {
   
        System.out.println("打印目标类信息");
    }
}

// 拦截器
public class MyInterceptor implements MethodInterceptor {
   
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
   
        System.out.println("------插入前置通知代码-------------");
        Object result = methodProxy.invokeSuper(o, objects);
        System.out.println("------插入后置处理代码-------------");
        return result;
    }
}

// 测试类
public class CglibProxyTest {
   
    public static void main(String[] args) {
   
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(TargetClass.class);
        enhancer.setCallback(new MyInterceptor());
        TargetClass proxy = (TargetClass) enhancer.create();
        proxy.targetInfo();
    }
}

拦截器 MyInterceptor​

  • 实现接口:实现了MethodInterceptor​接口,该接口要求实现intercept​方法。
  • 参数说明:

    • o: 代理对象实例。
    • method: 被拦截的方法对象。
    • objects: 方法参数数组。
    • methodProxy: 方法代理对象,用于调用父类的方法。
  • 功能:在intercept​方法中,首先执行前置处理(即打印“插入前置通知代码”),然后通过methodProxy.invokeSuper(o, objects)​调用原始方法,最后执行后置处理(即打印“插入后置处理代码”)。

‍

测试类 CglibProxyTest

  • Enhancer: 这是CGLIB的核心类之一,用于生成代理对象。

    • setSuperclass(TargetClass.class): 设置要代理的基类(目标类)。
    • setCallback(new MyInterceptor()): 设置回调拦截器,这里是之前定义的MyInterceptor​。
    • create(): 创建代理对象并返回。
  • 测试流程:

    • 使用Enhancer​创建一个TargetClass​的代理对象。
    • 通过代理对象调用targetInfo()​方法。
    • 实际上,这个方法调用会被MyInterceptor.intercept​方法拦截,在执行目标方法前后分别打印出前置和后置处理的信息。

‍

执行结果

当你运行CglibProxyTest​类的main​方法时,程序将输出如下内容:

------插入前置通知代码-------------
打印目标类信息
------插入后置处理代码-------------

image​

‍

通过本文的介绍,我们深入了解了Java动态代理的工作原理、应用场景以及其实现方式。无论是JDK动态代理还是CGLIB,它们都在现代Java开发中扮演着重要角色,特别是在AOP和微服务架构中。

希望这些知识能帮助你在实际项目中灵活运用动态代理技术,提升代码的可维护性和扩展性。

相关实践学习
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
目录
相关文章
|
安全 Oracle Java
JAVA高级开发必备·卓伊凡详细JDK、JRE、JVM与Java生态深度解析-形象比喻系统理解-优雅草卓伊凡
JAVA高级开发必备·卓伊凡详细JDK、JRE、JVM与Java生态深度解析-形象比喻系统理解-优雅草卓伊凡
870 0
JAVA高级开发必备·卓伊凡详细JDK、JRE、JVM与Java生态深度解析-形象比喻系统理解-优雅草卓伊凡
|
Oracle Java 关系型数据库
新手必看:Java 开发环境搭建之 JDK 与 Maven
本文分享了 Java 学习中 JDK 安装配置与 Maven 使用的入门知识,涵盖 JDK 下载安装、环境变量设置、Maven 安装配置及本地仓库与镜像设置,帮助新手快速搭建 Java 开发环境。
2007 0
|
安全 Java 微服务
Java 最新技术和框架实操:涵盖 JDK 21 新特性与 Spring Security 6.x 安全框架搭建
本文系统整理了Java最新技术与主流框架实操内容,涵盖Java 17+新特性(如模式匹配、文本块、记录类)、Spring Boot 3微服务开发、响应式编程(WebFlux)、容器化部署(Docker+K8s)、测试与CI/CD实践,附完整代码示例和学习资源推荐,助你构建现代Java全栈开发能力。
1210 1
|
安全 Java API
Java最新技术(JDK 11+) 及以上 Java 最新技术之集合框架实操应用详解
本示例基于Java最新技术(JDK 11+),涵盖集合框架的核心功能,结合Java 8+特性(如Stream API、Lambda表达式)与并发编程最佳实践。内容包括:List操作(初始化、Lambda过滤、Stream处理)、Map操作(流式过滤、ConcurrentHashMap原子操作、并行流)、Set操作(TreeSet排序、CopyOnWriteArraySet并发安全)、Queue/Deque操作(优先队列、双端队列)以及高级聚合操作(集合转换、分组统计、平均值计算)。 [代码下载](https://pan.quark.cn/s/14fcf913bae6)
323 4
|
Java Spring
JDK动态代理和CGLIB动态代理的区别
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理: ● JDK动态代理只提供接口的代理,不支持类的代理Proxy.newProxyInstance(类加载器, 代理对象实现的所有接口, 代理执行器) ● CGLIB是通过继承的方式做的动态代理 , 如果某个类被标记为final,那么它是无法使用 CGLIB做动态代理的。Enhancer.create(父类的字节码对象, 代理执行器)
|
监控 Java API
JDK动态代理和CGLIB动态代理
Java动态代理允许在运行时创建代理对象,增强或拦截目标类方法的执行。主要通过两种方式实现:JDK动态代理和CGLIB动态代理。JDK动态代理基于接口,利用`java.lang.reflect.Proxy`类和`InvocationHandler`接口;CGLIB则通过字节码技术生成目标类的子类作为代理,适用于未实现接口的类。两者均用于在方法执行前后添加额外逻辑,如日志记录、权限控制等,广泛应用于AOP框架中。
592 2
|
Java 数据库连接
Java之jdk和CGLib实现动态代理
Java之jdk和CGLib实现动态代理
292 0
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
705 1
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
632 1