一、关于动态代理
1.1 简介
动态代理是一种在 运行时动态生成代理类 的技术,无需手动编写代理类代码。它通过拦截目标方法的调用,实现对核心逻辑的 无侵入式增强(如日志、事务、权限控制等)。
1.2 发展
- 早期概念与雏形
- 1995年 - Java诞生:Java最初发布时,并没有直接支持动态代理的功能。然而,随着面向对象编程(OOP)理念的普及,开发者开始寻求更灵活的方式来处理代码复用和模块化问题。
- 1998年 - JDK 1.2引入反射API:虽然JDK 1.2并未直接提供动态代理的支持,但它引入了反射API (
java.lang.reflect
),这为后续动态代理的实现奠定了基础。反射允许程序在运行时检查或"反射"自身,并操作内部属性和方法。
- 正式引入动态代理(JDK 1.3)
-
2000年 - JDK 1.3:这是Java动态代理的一个重要里程碑。JDK 1.3正式引入了
java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口,提供了对动态代理的支持。开发者可以通过这些API在运行时创建实现了指定接口的代理对象。- Proxy类:用于创建动态代理实例。
- InvocationHandler接口:所有方法调用都将通过此接口传递给代理对象。
这一版本使得Java能够在运行时生成代理类,而无需事先定义具体的代理类,极大地增强了开发的灵活性。
- 增强与优化(JDK 1.4及之后)
- 2002年 - JDK 1.4:尽管JDK 1.4并没有对动态代理本身做出重大改进,但这一时期Java整体性能的提升以及对反射机制的进一步优化,间接促进了动态代理的应用与发展。
- 2004年 - JDK 5 (1.5) :引入了注解(Annotations)、泛型(Generics)等新特性,这些特性虽然不是专门为动态代理设计的,但它们扩展了动态代理的应用场景。例如,利用注解可以更方便地配置代理行为。
- 2006年 - JDK 6 (1.6) :继续对反射机制进行优化,提升了动态代理的性能。同时,随着Spring框架等流行框架的广泛使用,动态代理技术得到了更广泛的实践和验证。
- 现代应用与扩展
- 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 应用场景
动态代理的应用场景非常广泛,以下是一些常见的用途:
- 日志记录:在方法执行前后记录日志。
- 事务管理:在方法执行前启动事务,执行后提交事务。
- 权限控制:在执行目标方法前进行权限校验。
- 延迟加载:代理可以延迟初始化目标对象,直到需要使用时才加载。
- 性能监控:监控方法执行的时间或次数。
二、JDK动态代理
2.1 简介
JDK动态代理是Java提供的一种在运行时创建代理对象的机制,它允许开发者在不修改原始类的情况下,为接口实现类添加额外的功能(如日志记录、权限检查等)。
这种机制基于java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口。
2.2 特点
- 灵活性高:可以在运行时动态创建代理对象,无需为每个具体类编写代理类。
- 减少代码量:避免了静态代理中为每个具体类编写代理类的繁琐工作。
- 增强功能:可以轻松地为多个不同接口的对象添加统一的横切关注点(如日志记录、事务管理等)。
- 仅限接口代理:JDK动态代理只能代理实现了接口的类。如果需要代理没有实现接口的类,则需要使用其他技术,如CGLIB库。
2.3 核心概念
JDK的动态代理需要了解两个类
-
Proxy类:位于
java.lang.reflect
包中,提供了用于创建动态代理类和实例的方法。Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
方法用于创建代理实例。 -
InvocationHandler接口:所有方法调用都将通过此接口传递给代理对象。该接口包含一个
invoke
方法,代理对象上的任何方法调用都会触发此方法。invoke()
方法接受三个参数:
proxy
:代理对象。method
:被调用的方法。args
:方法的参数。
Proxy ( 代理)
InvocationHandler(调用处理程序)
2.4 工作原理
JDK 动态代理通过反射机制生成代理类,代理类实现目标接口并将方法调用转发到 InvocationHandler
实现类。具体的工作原理如下:
- 创建代理类:
Proxy.newProxyInstance()
方法会创建一个动态代理类。这个代理类实现了指定的接口,并将所有的方法调用委派给InvocationHandler
接口的invoke()
方法。 - 方法拦截:当代理类的方法被调用时,JDK 会通过反射机制调用
InvocationHandler
的invoke()
方法。 - 调用目标方法:在
invoke()
方法中,我们可以加入自定义的逻辑(例如日志记录、权限控制等),然后使用反射调用目标对象的方法。
2.5 实现步骤
以下是使用JDK动态代理的基本步骤:
- 定义业务接口:首先定义一个或多个业务接口。
- 实现业务逻辑的具体类:编写具体的业务逻辑实现类。
- 实现InvocationHandler接口:编写一个类实现
InvocationHandler
接口,在invoke
方法中定义代理行为。 - 创建代理实例:使用
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 {@Overridepublic void addUser(String name) {System.out.println("添加用户:" + name);}
}// 3. 实现InvocationHandler接口
class UserServiceInvocationHandler implements InvocationHandler {private Object target;public UserServiceInvocationHandler(Object target) {this.target = target;}@Overridepublic 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();// 创建InvocationHandlerInvocationHandler 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
:方法调用时传入的实际参数列表。
输出结果:
前置操作:权限检查
添加用户:张三
后置操作:日志记录
三、CGLIB 动态代理
3.1 简介
CGLIB(Code Generation Library)是一个强大的字节码生成库,广泛用于在运行时动态生成类的子类作为代理对象。与 JDK 动态代理不同,CGLIB 可以代理普通类,而不仅仅是接口
3.2 发展
CGLIB(Code Generation Library)是一个强大的字节码生成库,最初的目的是通过动态生成字节码来增强 Java 程序的功能,尤其是实现动态代理。CGLIB 作为一种基于继承的动态代理技术,得到了广泛应用,特别是在 Spring 框架中。它的主要发展历程如下:
- CGLIB 的初始版本与诞生
CGLIB 最初由 Rod Johnson(Spring 框架的创始人)在 2000 年代初期为解决 Java 中动态代理的不足而开发。它的设计初衷是为了解决 Java 中基于接口的动态代理(如 JDK 动态代理)存在的局限性——JDK 动态代理只能代理实现了接口的类,而 CGLIB 动态代理不依赖接口,可以代理任何类,甚至没有接口的类。
- CGLIB 作为 Spring 的核心组件
在 Spring Framework 早期版本中,CGLIB 被用作 AOP(面向切面编程)的核心实现之一。在 Spring AOP 中,CGLIB 被广泛使用来生成代理对象,从而在方法调用时插入切面逻辑,如事务管理、日志记录等。
特别是在 Spring 中的 代理模式 和 AOP(Aspect-Oriented Programming) 的实现,CGLIB 提供了非常有效的方式来拦截对象方法的调用,并进行增强。Spring 将 CGLIB 与 JDK 动态代理结合使用,具体选择哪种代理方式取决于目标对象是否实现了接口。
- CGLIB 发展与更新
CGLIB 项目的版本演化主要体现在性能优化、功能增强、兼容性提升以及 bug 修复等方面。CGLIB 在 2000 年代中期到 2010 年前后获得了较为稳定的版本,但主要的变动集中在:
- 性能优化:为了提高字节码生成的效率,CGLIB 做了许多优化,例如通过缓存机制避免重复生成相同类的字节码。
- 增强功能:CGLIB 不仅支持方法拦截,还支持更为复杂的字节码修改,比如动态代理字段、构造函数等。
- 兼容性提升:随着 Java 语言和 JVM(Java Virtual Machine)的演进,CGLIB 不断修复兼容性问题,使其能够支持新的 Java 特性和版本。
- CGLIB 的分支与衍生
随着 Spring 的发展,CGLIB 动态代理的使用逐渐被限制在特定场景中。例如,Spring 在后期引入了 Java 的 JDK 动态代理,并根据对象的特性(是否实现了接口)来选择代理方式。
与此同时,随着 Java 代理技术的发展和对性能要求的提升,CGLIB 的某些特性逐渐被更高效的技术所取代,比如 JDK 动态代理、Java Instrumentation API 等。
尽管如此,CGLIB 仍然在某些场景中(如需要代理没有接口的类)保持着其不可替代的地位。它的设计和思想影响了后来的许多字节码增强技术。
- CGLIB 与 Spring 4.x 后期的变化
随着 Spring 4.x 版本的推出,Spring 框架逐渐更多地使用了 JDK 动态代理,并且在 Spring AOP 中开始默认使用基于接口的 JDK 动态代理。当目标对象实现了接口时,Spring 会使用 JDK 动态代理;当目标对象没有实现接口时,Spring 才会退回使用 CGLIB 代理。
Spring 对 CGLIB 的使用有所减少,但 CGLIB 仍然作为 Spring AOP 的重要选择之一存在,尤其是在需要代理的类没有接口时,CGLIB 仍然是最佳选择。
- CGLIB 的当前状态与替代技术
- Java 8 的引入:Java 8 引入了默认方法(
default methods
),使得许多使用 CGLIB 的场景能够通过接口的方式解决。Java 8 默认方法让 JDK 动态代理的使用场景更加广泛,从而减少了对 CGLIB 的需求。 - 字节码操作库的进展:现代的字节码增强工具,如 Byte Buddy、ASM、Javassist,也为字节码增强和代理提供了更为灵活和强大的功能。特别是 Byte Buddy,作为一个轻量级的字节码操作库,提供了比 CGLIB 更丰富的功能,并且在许多场景下成为了替代选择。
尽管如此,CGLIB 仍然在一些需要继承的场景中得到使用,特别是在一些成熟的 Java 项目中,它依旧是代理类生成的核心库之一。
- 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
的一种实现。
3.6 实现步骤
- 定义业务类:首先需要有一个具体的业务类,该类可能包含一些需要被拦截的方法。
- 创建拦截器:实现
MethodInterceptor
接口,并重写intercept
方法,在这里可以编写自定义逻辑,比如添加前置处理、后置处理等。 - 生成代理类:使用
Enhancer
类设置父类为业务类,并指定回调拦截器。然后调用create()
方法生成代理实例。
3.7 实现示例
示例1
// 业务类
public class TargetClass {public void targetInfo() {System.out.println("打印目标类信息");}
}// 拦截器
public class MyInterceptor implements MethodInterceptor {@Overridepublic 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
方法时,程序将输出如下内容:
------插入前置通知代码-------------
打印目标类信息
------插入后置处理代码-------------
通过本文的介绍,我们深入了解了Java动态代理的工作原理、应用场景以及其实现方式。无论是JDK动态代理还是CGLIB,它们都在现代Java开发中扮演着重要角色,特别是在AOP和微服务架构中。
希望这些知识能帮助你在实际项目中灵活运用动态代理技术,提升代码的可维护性和扩展性。