目录
主要区别
概念范围
-
JVM动态代理:是一个更广泛的概念,指的是在Java虚拟机(JVM)层面上实现的动态代理技术。它包括了所有在JVM上实现的动态代理技术,不仅限于JDK提供的实现方式。JVM动态代理提供了一种灵活且强大的方式来扩展和修改类的行为,而无需修改原始类的代码。
-
JDK动态代理:是JVM动态代理的一种具体实现方式,基于Java的反射机制。它允许在运行时动态地创建代理类和代理对象,用于替代原始对象进行方法调用,并可以在方法调用前后添加额外的逻辑。JDK动态代理是Java标准库提供的一种动态代理机制。
实现方式
-
JVM动态代理:可以通过多种方式实现,包括但不限于JDK自带的Proxy类、第三方库如CGLIB和ASM等。这些实现方式可能在性能、功能或使用场景上有所不同。
-
JDK动态代理:具体实现依赖于Java的反射机制。它要求被代理的对象必须实现至少一个接口,代理类通过实现与被代理对象相同的接口来拦截方法调用。JDK动态代理的核心类是
java.lang.reflect.Proxy
,它提供了创建代理类和代理对象的静态方法。
使用场景
-
JVM动态代理:由于涵盖了多种实现方式,因此适用于更广泛的场景。例如,当需要代理没有实现接口的类时,可以使用CGLIB等基于字节码技术的动态代理实现。
-
JDK动态代理:主要适用于代理实现了接口的类。它广泛用于面向切面编程(AOP)、日志记录、事务管理、权限控制等场景。通过JDK动态代理,可以在不修改原始代码的情况下,为方法调用添加额外的逻辑。
性能考虑
-
JVM动态代理:性能可能因实现方式而异。例如,基于反射的JDK动态代理在性能上可能不如基于字节码技术的CGLIB动态代理。
-
JDK动态代理:由于基于反射机制,性能相对较低。但在Java 8及以后的版本中,反射机制的性能得到了优化,使得JDK动态代理的性能有所提升。尽管如此,在性能敏感的场景中,仍然需要考虑使用其他更高效的动态代理实现方式。
JDK动态代理介绍
核心思想是创建一个代理对象,该对象在运行时动态生成,并实现了与目标对象相同的接口。当客户端代码调用代理对象的方法时,动态代理机制会将方法调用转发到目标对象,并在调用前后执行指定的逻辑。
实现步骤
1. 定义接口
首先,需要定义一个或多个接口,目标对象(被代理的对象)必须实现这些接口。代理对象将实现这些接口,以便能够代理目标对象的方法调用。
public interface MyInterface {
void myMethod();
}
2. 实现目标对象
实现一个类,使其实现前面定义的接口。这个类就是需要被代理的目标对象。
public class MyTarget implements MyInterface {
@Override
public void myMethod() {
System.out.println("Executing myMethod in MyTarget");
}
}
3. 实现InvocationHandler
接口
创建一个处理器类,实现java.lang.reflect.InvocationHandler
接口。这个接口包含一个invoke
方法,用于处理对代理对象方法的调用。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyInvocationHandler implements InvocationHandler {
private final Object target; // 目标对象
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method call");
Object result = method.invoke(target, args); // 调用目标对象的方法
System.out.println("After method call");
return result;
}
}
4. 创建代理对象
使用java.lang.reflect.Proxy
类的newProxyInstance
方法,根据指定的类加载器、接口数组和InvocationHandler
实例,动态生成代理类对象。
import java.lang.reflect.Proxy;
public class DynamicProxyDemo {
public static void main(String[] args) {
MyTarget target = new MyTarget(); // 目标对象
MyInvocationHandler handler = new MyInvocationHandler(target); // 处理器对象
// 动态生成代理对象
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
handler
);
// 通过代理对象调用目标对象的方法
proxy.myMethod();
}
}
执行流程
1. 创建代理对象
在运行时,通过Proxy.newProxyInstance
方法,JVM根据指定的类加载器、接口数组和InvocationHandler
实例,动态生成代理类对象。
2. 方法调用拦截
当客户端代码调用代理对象的方法时,JVM会拦截这次调用,并将调用转发到InvocationHandler
对象的invoke
方法。
3. 执行额外逻辑
在invoke
方法中,可以执行额外的逻辑,例如日志记录、安全检查、事务管理等。
4. 调用目标方法
在invoke
方法中,通过反射机制调用目标对象的方法,并将调用结果返回给客户端代码。
应用场景
1. 面向切面编程(AOP)
在Spring框架中,AOP通过动态代理实现横切关注点的织入,如事务管理、日志记录等。
2. 远程过程调用(RPC)
在RPC框架中,动态代理用于生成远程服务的代理对象,客户端通过调用代理对象的方法,实现对远程服务的调用。
3. 缓存机制
通过动态代理,可以在方法调用前后添加缓存逻辑,提高应用的性能。
注意事项
1. 性能开销
动态代理通过反射机制实现方法调用,相对于直接调用目标对象的方法,存在一定的性能开销。
2. 接口限制
JDK动态代理是基于接口的,因此只能代理实现了接口的类。如果需要代理没有实现接口的类,可以考虑使用CGLIB等第三方库。
3. 异常处理
在invoke
方法中,需要妥善处理可能抛出的异常,避免影响代理对象的正常使用。
JVM动态代理之CGLIB
CGLIB(Code Generation Library)是一个强大的高性能代码生成库,主要用于在运行时动态生成类和代理对象。CGLIB动态代理通过生成目标类的子类,并在子类中重写目标类的方法来实现对方法调用的拦截。
核心组件
1. Enhancer类
Enhancer类是CGLIB中用于创建代理对象的核心类。它类似于一个工厂,负责生产代理对象。通过设置Enhancer的一些属性,可以指定目标对象、拦截器等信息,然后调用其create()
方法来创建代理对象。
2. MethodInterceptor接口
MethodInterceptor接口定义了拦截器的方法。拦截器是CGLIB动态代理的关键部分,用于在目标方法执行前后或异常处理时添加额外的逻辑。实现了该接口的类需要实现intercept()
方法,在这个方法中编写增强代码。
3. Callback接口及其实现类
Callback接口是一个更通用的回调接口,MethodInterceptor是它的一个子接口。除了MethodInterceptor,CGLIB还提供了其他一些Callback实现类,用于不同的目的,比如用于处理方法调用结果的FixedValue等。可以将多个Callback实现类组合起来使用,以实现更复杂的代理逻辑。
实现步骤
1. 创建Enhancer对象
首先,需要创建一个Enhancer对象。这个对象将用于生成代理类。
2. 设置目标类
通过调用Enhancer的setSuperclass()
方法,设置目标类的超类。这意味着生成的代理类将继承目标类。
3. 设置拦截器
通过调用Enhancer的setCallback()
方法,设置一个实现了MethodInterceptor接口的回调对象。该回调对象将负责拦截方法调用并执行额外的逻辑。
4. 创建代理对象
调用Enhancer的create()
方法,生成代理类的实例。这个实例就是最终的代理对象。
5. 调用代理对象的方法
当调用代理对象的方法时,该方法调用将被拦截并转发到回调对象的intercept()
方法中。在intercept()
方法中,可以执行额外的逻辑(如日志记录、权限检查等),然后通过调用MethodProxy.invokeSuper()
方法执行目标类的原始方法。
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
class TargetClass {
public void method1() {
System.out.println("执行目标方法 method1");
}
public void method2() {
System.out.println("执行目标方法 method2");
}
}
class MyInterceptor 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 CglibProxyTest {
public static void main(String[] args) {
// 创建 Enhancer 对象
Enhancer enhancer = new Enhancer();
// 设置目标类
enhancer.setSuperclass(TargetClass.class);
// 设置拦截器
enhancer.setCallback(new MyInterceptor());
// 创建代理对象
TargetClass proxy = (TargetClass) enhancer.create();
// 调用代理对象的方法
proxy.method1();
proxy.method2();
}
}
注意事项
1. final类和方法
由于CGLIB通过继承方式创建代理类,因此目标类不能是final的,否则会导致代理失败。此外,目标类中的final方法也无法被代理,因为final方法不能被重写。
2. 性能开销
CGLIB在生成代理类时需要进行字节码操作,这可能会带来一定的性能开销。因此,在性能敏感的场景中,需要谨慎使用CGLIB动态代理。
3. 依赖库
CGLIB是一个第三方库,使用时需要将其添加到项目的依赖中。通常,可以通过Maven或Gradle等构建工具来管理CGLIB的依赖。