动态代理是如何实现的?JDK Proxy 和 CGLib 有什么区别
1. 概述
无论是日志框架或 Spring 框架,它们都包含了动态代理的实现代码。动态代理是程序在运行期间动态构建代理对象和动态调用代理方法的一种机制
动态代理的常用实现方式是反射。反射机制是指程序在运行期间可以访问、检测和修改其本身状态或行为的一种能力,使用反射我们可以调用任意一个类对象,以及类对象中包含的属性及方法。
动态代理不止有反射一种实现方式,例如,动态代理可以通过 CGLib 来实现,而 CGLib 是基于 ASM(一个 Java 字节码操作框架)而非反射实现的。动态代理是一种行为方式,而反射或 ASM 只是它的一种实现手段而已
JDK Proxy 和 CGLib 的区别主要体现在以下几个方面:
-
JDK Proxy是Java语言自带的功能,无需通过加载第三方类实现; -
JDK Proxy是通过拦截器加反射的方式实现的; -
JDK Proxy只能代理实现接口的类; -
CGLib是第三方提供的工具,基于ASM实现的,性能比较高; -
CGLib无需通过接口来实现,它是通过实现子类的方式来完成调用的
2. JDK Proxy 基于接口的动态代理实现
JDK Proxy 动态代理的实现无需引用第三方类,只需要实现 InvocationHandler 接口,重写 invoke() 方法即可,整个实现代码如下所示
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* JDK Proxy 相关示例
*/
public class ProxyExample {
static interface Car {
void running();
}
static class Bus implements Car {
@Override
public void running() {
System.out.println("The bus is running.");
}
}
static class Taxi implements Car {
@Override
public void running() {
System.out.println("The taxi is running.");
}
}
/**
* JDK Proxy
*/
static class JDKProxy implements InvocationHandler {
private Object target; // 代理对象
// 获取到代理对象
public Object getInstance(Object target) {
this.target = target;
// 取得代理对象
//newProxyInstance的参数
//ClassLoader:类加载器,用于加载代理对象字节码,和被代理对象使用相同的类加载器
//字节码数组,用于让代理对象和被代理对象有相同的方法,传被代理对象的接口就行
//InvocationHandler 如何实现代理
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
/**
* 执行被代理对象的任何接口方法都会经过该方法
* @param proxy 代理对象
* @param method 当前执行的方法
* @param args 方法的参数
* @return 和被代理对象有相同的返回值
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws InvocationTargetException, IllegalAccessException {
System.out.println("动态代理之前的业务处理.");
Object result = method.invoke(target, args); // 执行调用方法(此方法执行前后,可以进行相关业务处理)
return result;
}
}
public static void main(String[] args) {
// 执行 JDK Proxy
JDKProxy jdkProxy = new JDKProxy();
Car carInstance = (Car) jdkProxy.getInstance(new Taxi());
carInstance.running();
}
//动态代理之前的业务处理.
//The taxi is running
}
可以看出 JDK Proxy 实现动态代理的核心是实现 Invocation 接口,我们查看 Invocation 的源码,会发现里面其实只有一个 invoke() 方法,源码如下:
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
这是因为在动态代理中有一个重要的角色也就是代理器,它用于统一管理被代理的对象,显然 InvocationHandler 就是这个代理器,而 invoke() 方法则是触发代理的执行方法,我们通过实现 Invocation 接口来拥有动态代理的能力
3. CGLib 基于子类的动态代理实现
在使用 CGLib 之前,要先在项目中引入 CGLib 框架
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
CGLib 实现代码如下:
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CGLibExample {
static class Car {
public void running() {
System.out.println("The car is running.");
}
}
/**
* CGLib 代理类
*/
static class CGLibProxy implements MethodInterceptor {
private Object target; // 代理对象
public Object getInstance(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
// 设置父类为实例类
enhancer.setSuperclass(this.target.getClass());
// 回调方法
enhancer.setCallback(this);
// 创建代理对象
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method,
Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("方法调用前业务处理.");
Object result = methodProxy.invokeSuper(o, objects); // 执行方法调用
return result;
}
}
// 执行 CGLib 的方法调用
public static void main(String[] args) {
// 创建 CGLib 代理类
CGLibProxy proxy = new CGLibProxy();
// 初始化代理对象
Car car = (Car) proxy.getInstance(new Car());
// 执行方法
car.running();
}
}
可以看出 CGLib 和 JDK Proxy 的实现代码比较类似,都是通过实现代理器的接口,再调用某一个方法完成动态代理的,唯一不同的是,CGLib 在初始化被代理类时,是通过 Enhancer 对象把代理对象设置为被代理类的子类来实现动态代理的。因此被代理类不能被关键字 final 修饰,如果被 final 修饰,再使用 Enhancer 设置父类时会报错,动态代理的构建会失败
本文深入探讨了动态代理的实现原理,对比分析了JDKProxy和CGLib两种动态代理方式的异同,通过代码示例详细讲解了它们的具体应用。


被折叠的 条评论
为什么被折叠?



