文章目录
一.动态代理是什么
动态代理是一种编程模式。通过动态代理,我们可以在运行时动态地创建一个代理对象,这个代理对象可以代表一个实际的对象,拦截对实际对象的所有方法调用,从而可以在调用实际方法前后添加自定义行为。
总结:代理对象拦截实际调用,在调用前后添加行为
二.jdk代理
工作原理
两个核心组件:Proxy
类和InvocationHandler
接口。
-
Proxy类:
Proxy
类是java.lang.reflect
包中的一部分,有一个静态方法newProxyInstance
,这个方法用于在运行时动态创建代理实例。newProxyInstance
方法需要三个参数:类加载器(用于加载代理类)、一组接口(代理类需要实现的接口)、以及一个调用处理器(当代理的方法被调用时,会转发到这个调用处理器上)。
-
InvocationHandler接口:
- 任何动态代理的实现都必须实现
InvocationHandler
接口,这个接口中只定义了一个方法invoke
,这个方法会在代理实例上的方法被调用时执行。 invoke
方法有三个参数:代理实例本身(通常不使用)、正在被调用的方法对象以及方法调用的参数数组。方法可以根据需要进行增强,并通过反射调用实际对象的同名方法。
- 任何动态代理的实现都必须实现
在Java中,使用JDK动态代理通常涉及以下几个主要步骤,这些步骤详细说明了如何创建和使用动态代理来拦截和增强特定的接口方法调用:
使用步骤
步骤 1: 定义接口
定义接口,这个接口是我们接下来需要做增强的接口
public interface Greeting {
void greet(String name);
}
步骤 2: 实现接口
创见实际类实现这个接口
public class EnglishGreeting implements Greeting {
public void greet(String name) {
System.out.println("Hello, " + name + "!");
}
}
步骤 3: 创建调用处理器
创建一个实现了InvocationHandler
接口的类。这个类里面的invoke方法里面可以写我们的增强方法
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class GreetingInvocationHandler implements InvocationHandler {
private Object target;
public GreetingInvocationHandler(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: 创建代理实例
使用Proxy.newProxyInstance
方法创建动态代理对象。
需要三个参数:一个类加载器,一个需要实现的接口数组,以及你创建的调用处理器:
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
Greeting realObject = new EnglishGreeting();
Greeting proxy = (Greeting) Proxy.newProxyInstance(
Greeting.class.getClassLoader(),
new Class[] { Greeting.class },
new GreetingInvocationHandler(realObject)
);
proxy.greet("John");
}
}
三.CGLIB代理
工作原理
CGLIB通过使用底层的ASM(一个Java字节码操纵框架)来操作字节码并生成新的类。它不是代理接口,而是在运行时生成给定类的一个子类,并在子类中覆盖父类的方法。通过这种方式,CGLIB可以在调用方法时插入自定义逻辑,而不改变原有类的代码。
关键特性
-
方法拦截:CGLIB使用
MethodInterceptor
接口来拦截对目标方法的调用。开发者需要实现这个接口,然后在intercept
方法中定义拦截逻辑。 -
无需接口:与JDK动态代理相比,CGLIB不需要目标对象实现任何接口,这是它的一个显著优势。
-
性能考虑:虽然CGLIB在某些情况下比JDK动态代理慢(例如创建代理实例的过程),但在代理方法调用的性能上,CGLIB通常可以提供与JDK动态代理相比更优或相当的性能。
使用示例
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
// 定义一个普通类
class SampleClass {
public void test() {
System.out.println("Hello CGLIB!");
}
}
// 实现MethodInterceptor接口
class SampleInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before method");
Object result = proxy.invokeSuper(obj, args);
System.out.println("After method");
return result;
}
}
// 使用CGLIB创建代理类
public class CglibDemo {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(SampleClass.class);
enhancer.setCallback(new SampleInterceptor());
SampleClass proxy = (SampleClass) enhancer.create();
proxy.test(); // Output includes before and after messages
}
}
在这个例子中,SampleClass
是一个没有实现任何接口的简单类。我们通过CGLIB的Enhancer
类创建了SampleClass
的代理,并通过SampleInterceptor
在原有方法执行前后添加了额外的输出。
四.对比
1. 接口与类
- JDK动态代理:仅能代理实现了接口的类。如果要使用JDK代理,类必须实现一个或多个接口。
- CGLIB:能代理没有实现接口的类。它通过在运行时生成目标类的子类来实现代理功能,从而可以代理任何类。
2. 性能
- JDK动态代理:在生成代理的性能上比CGLIB快,因为它仅仅是反射调用接口的方法。但是在方法调用上,每次都通过反射,可能会稍微慢一些。
- CGLIB:生成代理的过程比较慢,因为需要通过字节码技术生成类的子类。但是,一旦类被创建,方法调用的性能可能优于或相当于JDK动态代理,因为它是直接调用类的方法,而不是通过反射。
3. 使用复杂度
- JDK动态代理:使用较为简单,只需要定义接口和实现
InvocationHandler
接口。Java标准库已内置支持,无需额外引入库。 - CGLIB:需要引入CGLIB库,使用稍微复杂一些,因为你需要处理更低层次的细节,如方法拦截和增强。配置也稍微复杂,因为需要理解如何操作字节码和类加载器。
4. 兼容性和限制
- JDK动态代理:不能对类本身进行代理,只能代理接口。如果目标对象没有实现任何接口,则无法使用JDK代理。
- CGLIB:无法代理声明为final的方法,因为这些方法不能被子类覆盖。此外,对于使用CGLIB增强的类,如果它有复杂的构造函数或者需要特殊方式初始化的类,可能会更加复杂。
5. 应用场景
- JDK动态代理:适用于那些已经定义了接口的模块。例如,在Java EE或Spring框架中,许多服务已经是基于接口的设计。
- CGLIB:适用于动态代理那些不便于或不能改造为接口的遗留代码。它也常用于那些由第三方库定义的类,这些类你无法修改其源代码来添加接口。