
在当今复杂的软件系统中,我们经常需要在不修改原有代码的情况下对方法进行增强。想象一下,当你需要给系统中所有方法添加日志记录、性能监控或事务管理时,难道要逐个修改每个方法吗?这显然不符合开闭原则,也会带来巨大的维护成本。
代理模式正是解决这类问题的绝佳方案。它就像现实生活中的中介,既能帮我们完成核心业务,又能在业务前后附加各种额外操作,而我们无需关心这些额外操作的具体实现细节。
在 Java 世界中,最常用的两种动态代理技术便是 JDK 动态代理和 CGLIB 代理。它们各有千秋,适用场景也不尽相同。本文将从底层原理到实战应用,全方位剖析这两种代理技术,带你深入理解它们的异同与精髓。
代理模式是一种结构型设计模式,它允许通过创建一个代理对象来控制对原对象的访问。代理对象可以在调用原对象方法前后添加额外的逻辑,而无需修改原对象的代码。
这种模式主要包含三个角色:
根据代理的创建时机和方式,代理模式可以分为:
静态代理虽然实现简单,但当需要代理的类和方法增多时,会产生大量重复代码,维护成本高。而动态代理则能很好地解决这个问题,它可以在运行时动态生成代理对象,大大提高了代码的灵活性和可维护性。
JDK 动态代理和 CGLIB 代理都属于动态代理技术,但它们的实现原理和适用场景有很大差异。
JDK 动态代理是 Java 自带的代理机制,从 JDK 1.3 版本就已经存在。它位于java.lang.reflect包下,主要通过Proxy类和InvocationHandler接口来实现。
与其他代理技术相比,JDK 动态代理的最大优势在于无需依赖第三方库,使用 JDK 自带的 API 即可实现。这使得它在各种 Java 应用中得到了广泛应用,尤其是在 Spring 等主流框架中。
JDK 动态代理的核心原理是在程序运行时,由 JVM 根据接口动态生成代理类的字节码,并加载到内存中。这个过程可以分为以下几个步骤:
InvocationHandler接口的实现InvocationHandler的invoke方法invoke方法中,可以添加额外逻辑,并决定是否调用被代理对象的原始方法需要注意的是,JDK 动态代理有一个重要限制:它只能为实现了接口的类创建代理对象。如果目标类没有实现任何接口,JDK 动态代理就无能为力了。
下面我们通过一个实际案例来深入理解 JDK 动态代理的使用方法。假设我们有一个用户服务,需要在不修改原有代码的情况下,为其添加日志记录和性能监控功能。
首先,我们定义一个用户服务接口,包含用户的增删改查操作:
package com.example.proxy.jdk;
/**
* 用户服务接口
*/
public interface UserService {
/**
* 添加用户
* @param username 用户名
* @param password 密码
* @return 是否添加成功
*/
boolean addUser(String username, String password);
/**
* 删除用户
* @param userId 用户ID
* @return 是否删除成功
*/
boolean deleteUser(Long userId);
/**
* 更新用户信息
* @param userId 用户ID
* @param username 新用户名
* @return 是否更新成功
*/
boolean updateUser(Long userId, String username);
/**
* 查询用户信息
* @param userId 用户ID
* @return 用户信息
*/
String getUserInfo(Long userId);
}接下来,我们实现这个接口,编写具体的业务逻辑:
package com.example.proxy.jdk;
import lombok.extern.slf4j.Slf4j;
/**
* 用户服务实现类
*/
@Slf4j
public class UserServiceImpl implements UserService {
@Override
public boolean addUser(String username, String password) {
log.info("添加用户:{}", username);
// 模拟数据库操作
try {
Thread.sleep(100);
} catch (InterruptedException e) {
log.error("添加用户异常", e);
return false;
}
return true;
}
@Override
public boolean deleteUser(Long userId) {
log.info("删除用户:{}", userId);
// 模拟数据库操作
try {
Thread.sleep(80);
} catch (InterruptedException e) {
log.error("删除用户异常", e);
return false;
}
return true;
}
@Override
public boolean updateUser(Long userId, String username) {
log.info("更新用户:{},新用户名:{}", userId, username);
// 模拟数据库操作
try {
Thread.sleep(120);
} catch (InterruptedException e) {
log.error("更新用户异常", e);
return false;
}
return true;
}
@Override
public String getUserInfo(Long userId) {
log.info("查询用户信息:{}", userId);
// 模拟数据库操作
try {
Thread.sleep(50);
} catch (InterruptedException e) {
log.error("查询用户信息异常", e);
return null;
}
return "用户信息:" + userId;
}
}现在,我们需要创建一个实现InvocationHandler接口的类,这个类将包含我们要添加的额外逻辑:
package com.example.proxy.jdk;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* 日志和性能监控处理器
*/
@Slf4j
@AllArgsConstructor
public class LoggingInvocationHandler implements InvocationHandler {
/**
* 被代理的目标对象
*/
private final Object target;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 方法调用前的逻辑:记录开始时间和日志
log.info("===== 方法 {} 开始调用,参数:{} =====",
method.getName(), Arrays.toString(args));
long startTime = System.currentTimeMillis();
Object result;
try {
// 调用目标对象的方法
result = method.invoke(target, args);
} catch (Exception e) {
// 处理方法调用异常
log.error("方法 {} 调用异常", method.getName(), e);
throw e;
} finally {
// 方法调用后的逻辑:记录结束时间和耗时
long endTime = System.currentTimeMillis();
log.info("===== 方法 {} 调用结束,耗时:{}ms =====",
method.getName(), (endTime - startTime));
}
return result;
}
}
为了更方便地创建代理对象,我们可以编写一个代理工厂类:
package com.example.proxy.jdk;
import java.lang.reflect.Proxy;
/**
* JDK动态代理工厂
*/
public class JdkProxyFactory {
/**
* 创建代理对象
* @param target 目标对象
* @return 代理对象
*/
@SuppressWarnings("unchecked")
public static <T> T createProxy(T target) {
// 创建InvocationHandler实例
LoggingInvocationHandler handler = new LoggingInvocationHandler(target);
// 使用Proxy类创建代理对象
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
handler
);
}
}Proxy.newProxyInstance方法需要三个参数:
最后,我们编写一个测试类来验证 JDK 动态代理的效果:
package com.example.proxy.jdk;
import lombok.extern.slf4j.Slf4j;
/**
* JDK动态代理测试类
*/
@Slf4j
public class JdkProxyTest {
public static void main(String[] args) {
// 创建目标对象
UserService userService = new UserServiceImpl();
// 创建代理对象
UserService proxy = JdkProxyFactory.createProxy(userService);
// 调用代理对象的方法
proxy.addUser("zhangsan", "123456");
proxy.getUserInfo(1L);
proxy.updateUser(1L, "lisi");
proxy.deleteUser(1L);
// 验证代理对象的类型
log.info("代理对象的类型:{}", proxy.getClass().getName());
log.info("代理对象是否实现了UserService接口:{}",
UserService.class.isInstance(proxy));
}
}运行上述代码,我们可以得到如下输出:
INFO com.example.proxy.jdk.LoggingInvocationHandler - ===== 方法 addUser 开始调用,参数:[zhangsan, 123456] =====
INFO com.example.proxy.jdk.UserServiceImpl - 添加用户:zhangsan
INFO com.example.proxy.jdk.LoggingInvocationHandler - ===== 方法 addUser 调用结束,耗时:105ms =====
INFO com.example.proxy.jdk.LoggingInvocationHandler - ===== 方法 getUserInfo 开始调用,参数:[1] =====
INFO com.example.proxy.jdk.UserServiceImpl - 查询用户信息:1
INFO com.example.proxy.jdk.LoggingInvocationHandler - ===== 方法 getUserInfo 调用结束,耗时:53ms =====
INFO com.example.proxy.jdk.LoggingInvocationHandler - ===== 方法 updateUser 开始调用,参数:[1, lisi] =====
INFO com.example.proxy.jdk.UserServiceImpl - 更新用户:1,新用户名:lisi
INFO com.example.proxy.jdk.LoggingInvocationHandler - ===== 方法 updateUser 调用结束,耗时:122ms =====
INFO com.example.proxy.jdk.LoggingInvocationHandler - ===== 方法 deleteUser 开始调用,参数:[1] =====
INFO com.example.proxy.jdk.UserServiceImpl - 删除用户:1
INFO com.example.proxy.jdk.LoggingInvocationHandler - ===== 方法 deleteUser 调用结束,耗时:81ms =====
INFO com.example.proxy.jdk.JdkProxyTest - 代理对象的类型:com.sun.proxy.$Proxy0
INFO com.example.proxy.jdk.JdkProxyTest - 代理对象是否实现了UserService接口:true
从输出结果可以看出,我们成功地在不修改UserServiceImpl代码的情况下,为其所有方法添加了日志记录和性能监控功能。代理对象的类型是com.sun.proxy.$Proxy0,这是 JVM 动态生成的类,并且它实现了UserService接口。
为了更深入地理解 JDK 动态代理的工作原理,我们来看一下Proxy.newProxyInstance方法的内部实现(基于 JDK 17):
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
// 检查h是否为null
Objects.requireNonNull(h);
// 复制接口数组(为了防止后续修改影响)
final Class<?>[] intfs = interfaces.clone();
// 进行安全检查
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
// 查找或生成指定接口的代理类
Class<?> cl = getProxyClass0(loader, intfs);
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
// 获取代理类的构造方法(参数为InvocationHandler)
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
// 如果构造方法是私有,尝试设置为可访问
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
// 创建代理实例
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}这个方法的核心步骤是:
其中,getProxyClass0方法负责生成代理类,它会先检查缓存,如果缓存中没有,则通过ProxyClassFactory来生成新的代理类。
ProxyClassFactory是Proxy的一个静态内部类,它通过defineClass0方法(一个本地方法)来生成代理类的字节码。生成的代理类大致会包含以下内容:
InvocationHandler类型的成员变量InvocationHandler的invoke方法equals、hashCode等方法的代理可能会有特殊行为final的,不能被继承CGLIB(Code Generation Library)是一个强大的高性能代码生成库,它可以在运行时动态生成类和接口。与 JDK 动态代理不同,CGLIB 不需要目标类实现任何接口,它通过继承目标类来创建代理对象。
CGLIB 的底层基于 ASM 字节码操作框架实现,因此它可以生成更高效的代理类。在许多主流框架中都能看到 CGLIB 的身影,如 Spring、Hibernate 等。
CGLIB 代理的核心原理是通过继承目标类,生成一个子类,并重写目标类中的非 final 方法。在重写的方法中,CGLIB 会添加额外的逻辑,并调用父类(目标类)的相应方法。
这个过程可以分为以下几个步骤:
由于 CGLIB 是通过继承来实现代理的,所以它有一个重要限制:不能代理 final 类和 final 方法,因为 final 类不能被继承,final 方法不能被重写。
下面我们使用 CGLIB 来实现与 JDK 动态代理相同的功能 —— 为用户服务添加日志记录和性能监控。
首先,我们需要在项目中添加 CGLIB 的依赖。如果使用 Maven,可以在pom.xml中添加以下依赖:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
CGLIB 不需要目标类实现接口,所以我们可以直接创建一个用户服务类:
package com.example.proxy.cglib;
import lombok.extern.slf4j.Slf4j;
/**
* 用户服务类(未实现接口)
*/
@Slf4j
public class UserService {
public boolean addUser(String username, String password) {
log.info("添加用户:{}", username);
// 模拟数据库操作
try {
Thread.sleep(100);
} catch (InterruptedException e) {
log.error("添加用户异常", e);
return false;
}
return true;
}
public boolean deleteUser(Long userId) {
log.info("删除用户:{}", userId);
// 模拟数据库操作
try {
Thread.sleep(80);
} catch (InterruptedException e) {
log.error("删除用户异常", e);
return false;
}
return true;
}
public boolean updateUser(Long userId, String username) {
log.info("更新用户:{},新用户名:{}", userId, username);
// 模拟数据库操作
try {
Thread.sleep(120);
} catch (InterruptedException e) {
log.error("更新用户异常", e);
return false;
}
return true;
}
public String getUserInfo(Long userId) {
log.info("查询用户信息:{}", userId);
// 模拟数据库操作
try {
Thread.sleep(50);
} catch (InterruptedException e) {
log.error("查询用户信息异常", e);
return null;
}
return "用户信息:" + userId;
}
// final方法不会被CGLIB代理
public final void finalMethod() {
log.info("这是一个final方法,不会被CGLIB代理");
}
}注意,我们添加了一个finalMethod方法,用于演示 CGLIB 不能代理 final 方法的特性。
CGLIB 通过MethodInterceptor接口来定义代理逻辑,类似于 JDK 动态代理中的InvocationHandler:
package com.example.proxy.cglib;
import lombok.extern.slf4j.Slf4j;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* 日志和性能监控拦截器
*/
@Slf4j
public class LoggingMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 方法调用前的逻辑:记录开始时间和日志
log.info("===== 方法 {} 开始调用,参数:{} =====",
method.getName(), Arrays.toString(args));
long startTime = System.currentTimeMillis();
Object result;
try {
// 调用目标对象的方法
// 使用proxy.invokeSuper调用父类(目标类)的方法
result = proxy.invokeSuper(obj, args);
} catch (Exception e) {
// 处理方法调用异常
log.error("方法 {} 调用异常", method.getName(), e);
throw e;
} finally {
// 方法调用后的逻辑:记录结束时间和耗时
long endTime = System.currentTimeMillis();
log.info("===== 方法 {} 调用结束,耗时:{}ms =====",
method.getName(), (endTime - startTime));
}
return result;
}
}与 JDK 动态代理不同的是,CGLIB 通过MethodProxy.invokeSuper方法来调用目标类的方法,而不是直接调用目标对象的方法。
接下来,我们创建一个 CGLIB 代理工厂,用于生成代理对象:
package com.example.proxy.cglib;
import net.sf.cglib.proxy.Enhancer;
/**
* CGLIB代理工厂
*/
public class CglibProxyFactory {
/**
* 创建代理对象
* @param targetClass 目标类的Class对象
* @return 代理对象
*/
@SuppressWarnings("unchecked")
public static <T> T createProxy(Class<T> targetClass) {
// 创建Enhancer对象,类似于JDK动态代理中的Proxy类
Enhancer enhancer = new Enhancer();
// 设置目标类为父类
enhancer.setSuperclass(targetClass);
// 设置回调对象,即MethodInterceptor的实现类
enhancer.setCallback(new LoggingMethodInterceptor());
// 创建并返回代理对象
return (T) enhancer.create();
}
}CGLIB 通过Enhancer类来生成代理类,主要步骤是:
Enhancer对象MethodInterceptor实现类)create方法生成代理对象最后,我们编写测试类来验证 CGLIB 代理的效果:
package com.example.proxy.cglib;
import lombok.extern.slf4j.Slf4j;
/**
* CGLIB代理测试类
*/
@Slf4j
public class CglibProxyTest {
public static void main(String[] args) {
// 创建代理对象
UserService proxy = CglibProxyFactory.createProxy(UserService.class);
// 调用代理对象的方法
proxy.addUser("zhangsan", "123456");
proxy.getUserInfo(1L);
proxy.updateUser(1L, "lisi");
proxy.deleteUser(1L);
proxy.finalMethod(); // 测试final方法
// 验证代理对象的类型
log.info("代理对象的类型:{}", proxy.getClass().getName());
log.info("代理对象是否是UserService的实例:{}",
UserService.class.isInstance(proxy));
log.info("代理对象的父类:{}", proxy.getClass().getSuperclass().getName());
}
}运行上述代码,我们可以得到如下输出:
INFO com.example.proxy.cglib.LoggingMethodInterceptor - ===== 方法 addUser 开始调用,参数:[zhangsan, 123456] =====
INFO com.example.proxy.cglib.UserService - 添加用户:zhangsan
INFO com.example.proxy.cglib.LoggingMethodInterceptor - ===== 方法 addUser 调用结束,耗时:103ms =====
INFO com.example.proxy.cglib.LoggingMethodInterceptor - ===== 方法 getUserInfo 开始调用,参数:[1] =====
INFO com.example.proxy.cglib.UserService - 查询用户信息:1
INFO com.example.proxy.cglib.LoggingMethodInterceptor - ===== 方法 getUserInfo 调用结束,耗时:52ms =====
INFO com.example.proxy.cglib.LoggingMethodInterceptor - ===== 方法 updateUser 开始调用,参数:[1, lisi] =====
INFO com.example.proxy.cglib.UserService - 更新用户:1,新用户名:lisi
INFO com.example.proxy.cglib.LoggingMethodInterceptor - ===== 方法 updateUser 调用结束,耗时:121ms =====
INFO com.example.proxy.cglib.LoggingMethodInterceptor - ===== 方法 deleteUser 开始调用,参数:[1] =====
INFO com.example.proxy.cglib.UserService - 删除用户:1
INFO com.example.proxy.cglib.LoggingMethodInterceptor - ===== 方法 deleteUser 调用结束,耗时:82ms =====
INFO com.example.proxy.cglib.UserService - 这是一个final方法,不会被CGLIB代理
INFO com.example.proxy.cglib.CglibProxyTest - 代理对象的类型:com.example.proxy.cglib.UserService$$EnhancerByCGLIB$$5f2a1b3a
INFO com.example.proxy.cglib.CglibProxyTest - 代理对象是否是UserService的实例:true
INFO com.example.proxy.cglib.CglibProxyTest - 代理对象的父类:com.example.proxy.cglib.UserService从输出结果可以看出,CGLIB 成功地为UserService类(未实现任何接口)创建了代理对象,并为其方法添加了日志记录和性能监控功能。值得注意的是,finalMethod方法没有被代理,因为它是 final 方法。
代理对象的类型是`com.example.proxy.cglib.UserService
EnhancerByCGLIB
5f2a1b3a,这是 CGLIB 动态生成的类,它的父类是UserService`。
CGLIB 的核心类是Enhancer,它负责生成代理类。Enhancer.create()方法最终会调用createHelper()方法,其核心逻辑如下:
private Object createHelper() {
// 验证和配置参数
preValidate();
// 生成代理类的Key
Object key = KEY_FACTORY.newInstance((superclass != null) ? superclass.getName() : null,
ReflectUtils.getNames(interfaces),
filter == ALL_ZERO ? null : filter,
callbackTypes,
useFactory,
interceptDuringConstruction,
serialVersionUID);
this.currentKey = key;
// 尝试从缓存中获取代理类
Class<?> gen = null;
try {
gen = (Class<?>) super.get(key);
} catch (IllegalArgumentException e) {
if (e.getMessage().indexOf("ClassNotFoundException") >= 0) {
// 忽略类未找到异常,将重新生成
} else {
throw e;
}
} catch (ClassCastException e) {
// 忽略类型转换异常,将重新生成
}
// 如果缓存中没有,则生成新的代理类
if (gen == null) {
// 生成代理类的字节码
byte[] b = strategy.generate(this, key);
String className = ClassNameReader.getClassName(new ClassReader(b));
ProtectionDomain protectionDomain = getProtectionDomain();
// 加载代理类
gen = ReflectUtils.defineClass(className, b, protectionDomain,
superclass.getClassLoader());
// 记录生成的代理类
recordGeneratedClass(gen, b);
}
// 创建代理实例
return firstInstance(gen);
}Enhancer通过Strategy接口来生成代理类的字节码,默认实现是DefaultGeneratorStrategy。generate方法会创建一个ClassGenerator,并通过它来生成代理类的字节码。
CGLIB 生成的代理类会继承目标类,并为每个非 final 方法生成一个重写方法。重写方法的大致逻辑是:
public Object addUser(String username, String password) {
MethodInterceptor interceptor = ...; // 获取MethodInterceptor实例
if (interceptor != null) {
return interceptor.intercept(this,
UserService.class.getMethod("addUser", String.class, String.class),
new Object[]{username, password},
methodProxy);
} else {
return super.addUser(username, password);
}
}
特性 | JDK 动态代理 | CGLIB 代理 |
|---|---|---|
实现方式 | 实现目标类的接口 | 继承目标类 |
底层技术 | JDK 反射机制 | ASM 字节码操作 |
代理对象类型 | 实现了目标接口的代理类 | 目标类的子类 |
方法调用方式 | 通过 InvocationHandler.invoke () | 通过 MethodInterceptor.intercept () |
调用目标方法 | 使用 Method.invoke () | 使用 MethodProxy.invokeSuper () |
特性 | JDK 动态代理 | CGLIB 代理 |
|---|---|---|
是否需要接口 | 是 | 否 |
能否代理 final 类 | 不能(但可以代理 final 类实现的接口) | 不能 |
能否代理 final 方法 | 不能(接口中的方法不能是 final) | 不能 |
能否代理静态方法 | 不能 | 不能 |
能否代理私有方法 | 不能 | 不能(但可以通过特殊配置实现) |
能否代理 protected 方法 | 不能(接口中没有 protected 方法) | 能 |
能否增强构造方法 | 不能 | 能(通过设置 interceptDuringConstruction) |
性能是选择代理技术时需要考虑的重要因素。我们通过一个简单的测试来比较 JDK 动态代理和 CGLIB 代理的性能差异。
package com.example.proxy.performance;
import lombok.extern.slf4j.Slf4j;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 代理性能测试类
*/
@Slf4j
public class ProxyPerformanceTest {
// 测试次数
private static final int TEST_COUNT = 10000000;
public static void main(String[] args) {
// 准备测试对象
TestService jdkTarget = new TestServiceImpl();
TestService jdkProxy = createJdkProxy(jdkTarget);
TestService cglibTarget = new TestService();
TestService cglibProxy = createCglibProxy();
// 预热(避免JIT编译影响测试结果)
warmUp(jdkProxy, cglibProxy);
// 测试JDK动态代理性能
long jdkTime = testJdkProxy(jdkProxy);
log.info("JDK动态代理调用{}次耗时:{}ms", TEST_COUNT, jdkTime);
// 测试CGLIB代理性能
long cglibTime = testCglibProxy(cglibProxy);
log.info("CGLIB代理调用{}次耗时:{}ms", TEST_COUNT, cglibTime);
// 计算性能差异
double ratio = (double) cglibTime / jdkTime;
log.info("CGLIB代理与JDK动态代理的性能比:{:.2f}", ratio);
}
/**
* 预热方法,避免JIT编译影响测试结果
*/
private static void warmUp(TestService jdkProxy, TestService cglibProxy) {
for (int i = 0; i < 10000; i++) {
jdkProxy.test();
cglibProxy.test();
}
}
/**
* 测试JDK动态代理性能
*/
private static long testJdkProxy(TestService proxy) {
long startTime = System.currentTimeMillis();
for (int i = 0; i < TEST_COUNT; i++) {
proxy.test();
}
return System.currentTimeMillis() - startTime;
}
/**
* 测试CGLIB代理性能
*/
private static long testCglibProxy(TestService proxy) {
long startTime = System.currentTimeMillis();
for (int i = 0; i < TEST_COUNT; i++) {
proxy.test();
}
return System.currentTimeMillis() - startTime;
}
/**
* 创建JDK动态代理
*/
private static TestService createJdkProxy(TestService target) {
return (TestService) Proxy.newProxyInstance(
TestService.class.getClassLoader(),
new Class[]{TestService.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(target, args);
}
}
);
}
/**
* 创建CGLIB代理
*/
private static TestService createCglibProxy() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(TestService.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) ->
proxy.invokeSuper(obj, args));
return (TestService) enhancer.create();
}
/**
* 测试接口
*/
public interface TestService {
void test();
}
/**
* 测试接口实现类
*/
public static class TestServiceImpl implements TestService {
@Override
public void test() {
// 空方法,仅用于测试
}
}
/**
* 测试类(用于CGLIB代理)
*/
public static class TestService {
public void test() {
// 空方法,仅用于测试
}
}
}
在 JDK 17 环境下,运行上述测试代码,得到的结果大致如下:
INFO com.example.proxy.performance.ProxyPerformanceTest - JDK动态代理调用10000000次耗时:156ms
INFO com.example.proxy.performance.ProxyPerformanceTest - CGLIB代理调用10000000次耗时:94ms
INFO com.example.proxy.performance.ProxyPerformanceTest - CGLIB代理与JDK动态代理的性能比:0.60从测试结果可以看出,在这个场景下,CGLIB 代理的性能比 JDK 动态代理高出约 40%。这是因为 CGLIB 生成的代理类是通过直接调用父类方法实现的,而 JDK 动态代理是通过反射调用目标方法,反射操作的开销相对较大。
但需要注意的是,这个测试结果是在方法体为空的情况下得到的。当方法体中有实际业务逻辑时,代理方式带来的性能差异会被稀释,甚至可以忽略不计。
此外,CGLIB 生成代理类的时间通常比 JDK 动态代理长,因为它需要生成更多的字节码。因此,在需要频繁创建代理对象的场景中,JDK 动态代理可能更有优势。
场景 | 推荐代理技术 | 原因 |
|---|---|---|
目标类实现了接口 | JDK 动态代理 | 无需依赖第三方库,实现简单 |
目标类未实现接口 | CGLIB 代理 | JDK 动态代理无法代理无接口的类 |
需要代理 final 方法 | 无法代理 | 两种代理技术都不能代理 final 方法 |
需要代理 static 方法 | 无法代理 | 两种代理技术都不能代理 static 方法 |
对代理类生成速度要求高 | JDK 动态代理 | JDK 动态代理生成代理类的速度更快 |
对代理方法调用性能要求高 | CGLIB 代理 | CGLIB 代理的方法调用性能更好 |
希望代理类能被继承 | CGLIB 代理 | JDK 动态代理生成的代理类是 final 的 |
项目中已使用 Spring 等框架 | 按框架默认选择 | Spring 默认对实现了接口的类使用 JDK 动态代理,否则使用 CGLIB 代理 |
Spring 框架广泛使用了动态代理技术来实现 AOP(面向切面编程)功能。Spring 会根据目标类的情况自动选择合适的代理技术:
proxy-target-class="true"强制 Spring 使用 CGLIB 代理Spring AOP 中的通知(Advice)本质上就是通过代理技术在目标方法前后添加的额外逻辑。例如,@Transactional注解就是通过 AOP 实现的,它会在目标方法执行前后添加事务管理的逻辑。
下面是一个 Spring AOP 的简单示例:
package com.example.spring.aop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
* 日志切面
*/
@Aspect
@Component
@Slf4j
public class LoggingAspect {
/**
* 环绕通知,用于记录方法执行时间
*/
@Around("execution(* com.example.spring.service.*.*(..))")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
// 执行目标方法
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
log.info("方法 {} 执行耗时:{}ms",
joinPoint.getSignature().getName(),
endTime - startTime);
return result;
}
}这个切面会为com.example.spring.service包下所有类的所有方法添加执行时间记录的功能,这背后就是通过动态代理技术实现的。
MyBatis 框架使用 JDK 动态代理来实现 Mapper 接口的代理对象。当我们调用 Mapper 接口的方法时,MyBatis 会生成一个代理对象,该代理对象会根据方法名和参数生成对应的 SQL 语句,并执行数据库操作。
MyBatis 的MapperProxy类实现了InvocationHandler接口,它的invoke方法会根据方法信息查找对应的MappedStatement,然后执行 SQL 操作。
下面是 MyBatis 中MapperProxy的核心代码(简化版):
public class MapperProxy<T> implements InvocationHandler, Serializable {
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 如果是Object类的方法,直接调用
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
// 从缓存中获取或创建MapperMethod
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 执行SQL操作
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
return methodCache.computeIfAbsent(method,
k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
}通过这种方式,MyBatis 让我们可以只定义接口而无需编写实现类,大大简化了数据库操作的代码。
Hibernate 使用 CGLIB 代理来实现延迟加载(Lazy Loading)功能。当我们加载一个实体对象时,Hibernate 并不会立即加载其关联的对象,而是会创建一个 CGLIB 代理对象。只有当我们真正访问关联对象的属性或方法时,Hibernate 才会执行数据库查询,加载实际的数据。
这种延迟加载机制可以大大提高应用程序的性能,避免不必要的数据库查询。
在实际应用中,我们有时需要为一个对象添加多种增强功能。例如,既需要添加日志记录,又需要添加性能监控,还需要添加事务管理。这时,我们可以使用多级代理的方式,为目标对象创建多个代理层,每层代理负责一种增强功能。
下面是一个多级代理的示例:
package com.example.proxy.multi;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
/**
* 多级代理示例
*/
@Slf4j
public class MultiLevelProxyDemo {
public static void main(String[] args) {
// 创建目标对象
UserService target = new UserServiceImpl();
// 创建第一级代理:日志代理
UserService logProxy = createLogProxy(target);
// 创建第二级代理:性能监控代理
UserService performanceProxy = createPerformanceProxy(logProxy);
// 创建第三级代理:权限校验代理
UserService securityProxy = createSecurityProxy(performanceProxy);
// 调用代理对象的方法
securityProxy.addUser("zhangsan", "123456");
}
/**
* 创建日志代理
*/
private static UserService createLogProxy(UserService target) {
return (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class[]{UserService.class},
new LogInvocationHandler(target)
);
}
/**
* 创建性能监控代理
*/
private static UserService createPerformanceProxy(UserService target) {
return (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class[]{UserService.class},
new PerformanceInvocationHandler(target)
);
}
/**
* 创建权限校验代理
*/
private static UserService createSecurityProxy(UserService target) {
return (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class[]{UserService.class},
new SecurityInvocationHandler(target)
);
}
/**
* 日志处理器
*/
static class LogInvocationHandler implements InvocationHandler {
private final Object target;
LogInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log.info("日志:方法 {} 被调用,参数:{}", method.getName(), Arrays.toString(args));
Object result = method.invoke(target, args);
log.info("日志:方法 {} 调用完成,返回值:{}", method.getName(), result);
return result;
}
}
/**
* 性能监控处理器
*/
static class PerformanceInvocationHandler implements InvocationHandler {
private final Object target;
PerformanceInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = method.invoke(target, args);
long endTime = System.currentTimeMillis();
log.info("性能:方法 {} 执行耗时:{}ms", method.getName(), endTime - startTime);
return result;
}
}
/**
* 权限校验处理器
*/
static class SecurityInvocationHandler implements InvocationHandler {
private final Object target;
SecurityInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log.info("权限:检查方法 {} 的调用权限", method.getName());
// 实际应用中这里会有真实的权限校验逻辑
if ("deleteUser".equals(method.getName())) {
log.warn("权限:没有删除用户的权限");
throw new SecurityException("没有删除用户的权限");
}
return method.invoke(target, args);
}
}
/**
* 用户服务接口
*/
public interface UserService {
boolean addUser(String username, String password);
boolean deleteUser(Long userId);
}
/**
* 用户服务实现类
*/
public static class UserServiceImpl implements UserService {
@Override
public boolean addUser(String username, String password) {
log.info("添加用户:{}", username);
return true;
}
@Override
public boolean deleteUser(Long userId) {
log.info("删除用户:{}", userId);
return true;
}
}
}
运行上述代码,输出结果如下:
INFO com.example.proxy.multi.MultiLevelProxyDemo$SecurityInvocationHandler - 权限:检查方法 addUser 的调用权限
INFO com.example.proxy.multi.MultiLevelProxyDemo$PerformanceInvocationHandler - 性能:方法 addUser 执行耗时:0ms
INFO com.example.proxy.multi.MultiLevelProxyDemo$LogInvocationHandler - 日志:方法 addUser 被调用,参数:[zhangsan, 123456]
INFO com.example.proxy.multi.MultiLevelProxyDemo$UserServiceImpl - 添加用户:zhangsan
INFO com.example.proxy.multi.MultiLevelProxyDemo$LogInvocationHandler - 日志:方法 addUser 调用完成,返回值:true
INFO com.example.proxy.multi.MultiLevelProxyDemo$PerformanceInvocationHandler - 性能:方法 addUser 执行耗时:1ms
INFO com.example.proxy.multi.MultiLevelProxyDemo$SecurityInvocationHandler - 权限:检查方法 addUser 的调用权限从输出结果可以看出,方法调用经过了三级代理,每层代理都添加了自己的增强逻辑。这种多级代理的方式可以使每种增强功能独立开来,便于维护和扩展。
代理模式和装饰器模式在结构上非常相似,都可以为对象添加增强功能。但它们的意图不同:
在实际应用中,我们可以将这两种模式结合起来使用,既可以控制访问,又可以动态添加功能。
下面是一个代理与装饰器模式结合使用的示例:
package com.example.proxy.decorator;
import lombok.extern.slf4j.Slf4j;
/**
* 代理与装饰器模式结合示例
*/
@Slf4j
public class ProxyDecoratorDemo {
public static void main(String[] args) {
// 创建原始对象
DataService dataService = new DataServiceImpl();
// 使用装饰器添加缓存功能
DataService cachedDataService = new CachedDataService(dataService);
// 创建代理对象添加日志功能
DataService proxyDataService = DataServiceProxy.createProxy(cachedDataService);
// 调用方法
proxyDataService.query("1");
proxyDataService.query("1"); // 第二次查询会使用缓存
}
/**
* 数据服务接口
*/
public interface DataService {
String query(String id);
}
/**
* 数据服务实现类
*/
public static class DataServiceImpl implements DataService {
@Override
public String query(String id) {
log.info("从数据库查询数据,ID:{}", id);
// 模拟数据库查询
try {
Thread.sleep(100);
} catch (InterruptedException e) {
log.error("查询异常", e);
}
return "数据:" + id;
}
}
/**
* 缓存装饰器,为数据服务添加缓存功能
*/
public static class CachedDataService implements DataService {
private final DataService dataService;
private final LruCache<String, String> cache = new LruCache<>(100);
public CachedDataService(DataService dataService) {
this.dataService = dataService;
}
@Override
public String query(String id) {
// 先从缓存中查询
String result = cache.get(id);
if (result != null) {
log.info("从缓存中获取数据,ID:{}", id);
return result;
}
// 缓存中没有,从原始服务查询
result = dataService.query(id);
// 存入缓存
cache.put(id, result);
log.info("数据存入缓存,ID:{}", id);
return result;
}
}
/**
* LRU缓存实现
*/
public static class LruCache<K, V> {
private final int maxSize;
private final java.util.LinkedHashMap<K, V> cache;
public LruCache(int maxSize) {
this.maxSize = maxSize;
this.cache = new java.util.LinkedHashMap<>(maxSize, 0.75f, true) {
@Override
protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {
return size() > maxSize;
}
};
}
public V get(K key) {
return cache.get(key);
}
public void put(K key, V value) {
cache.put(key, value);
}
}
/**
* 数据服务代理类,添加日志功能
*/
public static class DataServiceProxy implements java.lang.reflect.InvocationHandler {
private final Object target;
private DataServiceProxy(Object target) {
this.target = target;
}
public static DataService createProxy(DataService target) {
return (DataService) java.lang.reflect.Proxy.newProxyInstance(
DataService.class.getClassLoader(),
new Class[]{DataService.class},
new DataServiceProxy(target)
);
}
@Override
public Object invoke(Object proxy, java.lang.reflect.Method method, Object[] args) throws Throwable {
log.info("日志:方法 {} 开始调用,参数:{}", method.getName(), args[0]);
long startTime = System.currentTimeMillis();
Object result = method.invoke(target, args);
long endTime = System.currentTimeMillis();
log.info("日志:方法 {} 调用结束,耗时:{}ms,结果:{}",
method.getName(), endTime - startTime, result);
return result;
}
}
}
在这个示例中,我们首先使用装饰器模式为DataService添加了缓存功能,然后使用代理模式为其添加了日志功能。这种结合方式既实现了功能的动态扩展,又实现了对方法调用的控制和增强。
在使用动态代理时,我们有时需要判断一个对象是否是代理对象,或者判断它是哪种类型的代理对象。
JDK 动态代理生成的代理类都实现了java.lang.reflect.Proxy类,可以通过以下方法判断:
public static boolean isJdkProxy(Object obj) {
return obj != null && Proxy.isProxyClass(obj.getClass());
}CGLIB 生成的代理类名称通常包含`
EnhancerByCGLIB
`可以通过以下方法判断:
public static boolean isCglibProxy(Object obj) {
return obj != null && obj.getClass().getName().contains("$$EnhancerByCGLIB$$");
}
有时我们需要从代理对象中获取被代理的原始对象,可以通过以下方法实现:
/**
* 从代理对象中获取原始对象
*/
public static Object getTarget(Object proxy) throws Exception {
if (!isJdkProxy(proxy) && !isCglibProxy(proxy)) {
return proxy; // 不是代理对象,直接返回
}
Field hField = proxy.getClass().getSuperclass().getDeclaredField("h");
hField.setAccessible(true);
Object invocationHandler = hField.get(proxy);
Field targetField = invocationHandler.getClass().getDeclaredField("target");
targetField.setAccessible(true);
return targetField.get(invocationHandler);
}需要注意的是,这种方法依赖于代理类的内部结构,可能会随着代理库版本的变化而失效。
在使用动态代理时,equals和hashCode方法的行为需要特别注意。
对于 JDK 动态代理,equals方法的默认实现是:如果两个代理对象是同一个实例,或者它们的InvocationHandler的equals方法返回 true,则返回 true。
对于 CGLIB 代理,equals方法的默认实现是继承自Object类的实现,即比较对象的内存地址。
在实际应用中,我们可能需要重写代理对象的equals和hashCode方法,使其与被代理对象的行为一致。
当需要序列化代理对象时,可能会遇到一些问题。JDK 动态代理生成的代理类是可序列化的,但 CGLIB 生成的代理类默认是不可序列化的。
要使 CGLIB 代理对象可序列化,需要进行特殊配置:
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);
enhancer.setCallback(new LoggingMethodInterceptor());
enhancer.setSerializable(true); // 设置可序列化
UserService proxy = (UserService) enhancer.create();此外,MethodInterceptor的实现类也需要实现Serializable接口。
JDK 动态代理和 CGLIB 代理都不能直接代理私有方法,因为私有方法不能被外部访问或重写。
如果需要增强私有方法,可以通过以下方式间接实现:
本文详细介绍了 JDK 动态代理和 CGLIB 代理的原理、实现方式和应用场景,主要内容包括:
通过本文的学习,相信你已经对 JDK 动态代理和 CGLIB 代理有了深入的理解,并能在实际项目中根据具体需求选择合适的代理技术。
随着 Java 语言的不断发展,代理技术也在不断演进。JDK 9 引入的java.lang.invoke包提供了更强大的动态方法调用能力,可能会在未来成为动态代理的新选择。
此外,GraalVM 等新一代 JVM 实现提供了更高效的即时编译和 Ahead-of-Time (AOT) 编译能力,这可能会改变不同代理技术的性能对比。
随着微服务和分布式系统的普及,代理技术在服务治理、熔断降级、分布式追踪等领域的应用也将越来越广泛。例如,服务网格(Service Mesh)技术就是基于代理模式实现的,它通过在服务之间插入代理层来实现流量管理、安全和可观测性等功能。