JDK动态代理和CGLIB动态代理的区别及适用场景解析
1. JDK动态代理和CGLIB动态代理的区别
1.1 前言
在软件开发中,代理模式是一种常见的设计模式,它提供了额外的中间层,以实现对目标对象的间接访问。通过使用代理,我们可以在不修改目标对象的情况下,对其进行功能扩展或增强。在实际项目中,我们经常会遇到需要使用动态代理的场景。而在Java中,最常用的两种动态代理实现方式分别是JDK动态代理和CGLIB动态代理。本文将详细介绍这两种动态代理方式的区别。
1.2 JDK动态代理
JDK动态代理是Java提供的一种基于接口的代理实现方式。它通过在运行时动态生成代理类,并实现目标接口,从而实现对目标对象的代理访问。JDK动态代理的核心类是java.lang.reflect.Proxy
。
1.2.1 使用案例
下面是一个使用JDK动态代理的示例代码:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 目标接口
interface UserService {
void saveUser(String username);
}
// 目标对象
class UserServiceImpl implements UserService {
@Override
public void saveUser(String username) {
System.out.println("保存用户:" + username);
}
}
// 代理处理器
class UserServiceProxy implements InvocationHandler {
private Object target;
public UserServiceProxy(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 target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new UserServiceProxy(target)
);
proxy.saveUser("Alice");
}
}
运行上述代码,可以看到以下输出结果:
代理前
保存用户:Alice
代理后
1.2.2 原理解析
JDK动态代理的原理是基于Java的反射机制。当使用JDK动态代理创建一个代理对象时,JDK会根据传入的目标对象和接口信息,生成一个新的类,在运行时动态加载并生成这个类的字节码,并创建代理对象。代理对象通过实现目标对象的接口,可以调用目标对象的方法,并在调用前后加入自定义的逻辑代码。
JDK动态代理的核心类是java.lang.reflect.Proxy
,它提供了静态方法newProxyInstance
来创建动态代理对象。Proxy.newProxyInstance
方法接受三个参数:ClassLoader、Interfaces、InvocationHandler。通过ClassLoader,可以指定生成代理类的加载器;通过Interfaces,可以指定代理类实现的接口;通过InvocationHandler,可以指定代理类的方法执行时的处理器。
在JDK动态代理中,代理类是通过接口的方式来实现的,因此目标对象必须实现接口才能使用JDK动态代理。
1.2.3 优点与缺点
JDK动态代理的优点是实现简单,无需引入第三方库,使用方便。它可以在运行时动态生成代理类,减少了编码量。另外,JDK动态代理是基于接口的代理,因此适用于对接口进行代理的场景。
然而,JDK动态代理也有一些局限性。首先,目标对象必须实现接口才能使用JDK动态代理,这在某些场景下会限制代理功能的使用。其次,由于JDK动态代理是基于接口的代理,因此不能对类的非公有方法进行代理。最后,JDK动态代理的性能相对较低,生成代理类的过程较为复杂,涉及到类的加载、生成字节码等操作。
1.3 CGLIB动态代理
CGLIB(Code Generation Library)是一个基于ASM(Java字节码操纵框架)的代码生成库,它可以在运行时对目标类进行增强。CGLIB动态代理不需要目标对象实现接口,通过生成目标对象的子类来实现对目标对象的代理访问。
1.3.1 使用案例
下面是一个使用CGLIB动态代理的示例代码:
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
// 目标类
class UserService {
public void saveUser(String username) {
System.out.println("保存用户:" + username);
}
}
// 代理拦截器
class UserServiceInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("代理前");
Object result = proxy.invokeSuper(obj, args);
System.out.println("代理后");
return result;
}
}
public class CglibDynamicProxyExample {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);
enhancer.setCallback(new UserServiceInterceptor());
UserService proxy = (UserService) enhancer.create();
proxy.saveUser("Bob");
}
}
运行上述代码,可以看到以下输出结果:
代理前
保存用户:Bob
代理后
1.3.2 原理解析
CGLIB动态代理利用ASM生成目标类的子类,并重写父类中的方法。在代理对象调用方法时,CGLIB通过MethodInterceptor拦截器来拦截方法的调用,并在调用前后加入自定义的逻辑代码。
CGLIB动态代理的核心类是net.sf.cglib.proxy.Enhancer
,它通过设置目标类和拦截器来创建代理对象。Enhancer.setSuperclass
方法用于指定目标类,Enhancer.setCallback
方法用于指定拦截器。
CGLIB动态代理不需要目标对象实现接口,因此对于没有实现接口的类也可以进行代理。
1.3.3 优点与缺点
CGLIB动态代理的主要优点是对目标对象没有任何要求,可以对任意类型的类进行代理,包括没有实现接口的类。另外,CGLIB动态代理不需要引入额外的依赖,使用方便。
然而,CGLIB动态代理也有一些限制。首先,由于CGLIB动态代理是通过生成目标类的子类来实现的,因此目标类不能声明为final。其次,CGLIB动态代理不能对final方法进行代理。最后,CGLIB动态代理的性能相对较低,涉及到类的继承和方法重写。
1.4 小结
JDK动态代理和CGLIB动态代理是常用的代理模式实现方式。它们的区别主要体现在实现原理和使用限制上。
JDK动态代理是基于接口的代理,需要目标对象实现接口才能使用。它通过生成代理类,在运行时动态加载并生成代理类的字节码,实现对目标对象的代理访问。JDK动态代理的优点是实现简单,无需引入第三方库;缺点是功能受限,不能对非公有方法进行代理,性能较低。
CGLIB动态代理不需要目标对象实现接口,通过生成目标对象的子类来实现对目标对象的代理访问。CGLIB动态代理的优点是可以对任意类型的类进行代理,使用方便;缺点是目标类不能声明为final,不能对final方法进行代理,性能较低。
选择使用哪种动态代理方式取决于具体的场景需求和项目要求。