动态代理在程序中的应用几乎无处不在,如何实现并使用动态代理是程序员必备技能之一,我们来聊聊动态代理的原理及其两种实现方式。
动态代理
- 代理模式是指一个对象A通过持有另一个对象B,可以具有B同样的行为的模式。为了对外开放协议,B往往实现了一个接口,A也会去实现接口。但是B是“真正”实现类,A则比较“虚”,他借用了B的方法去实现接口的方法。A虽然是“伪军”,但它可以增强B,在调用B的方法前后都做些其他的事情。
- 类A写死持有B,就是B的静态代理。如果A代理的对象是不确定的,就是动态代理。
- 动态代理的常用实现方式是反射,典型的是JDK Proxy(反射) 和 CGLib(ASM)。
- 这里推荐一篇讲解撒杏仁的知乎回答,将动态代理和静态代理讲的非常形象。
- 动态代理与静态代理的区别是静态代理只能针对特定一种产品(蛋糕、面包、饼干、酸奶)做某种代理动作(撒杏仁),而动态代理则可以对所有类型产品(蛋糕、面包、饼干、酸奶等)做某种代理动作(撒杏仁)。
JDK Proxy 和 CGLib
区别
- JDK Proxy 是 Java 语言自带的功能,无需通过加载第三方类实现;
- JDK Proxy 是通过拦截器加反射的方式实现的;
- JDK Proxy 只能代理继承接口的类;
- CGLib 是第三方提供的工具,基于 ASM 实现的,性能比较高;
- CGLib 无需通过接口来实现,它是通过实现子类的方式来完成调用的;
- JDK Proxy是“对象的代理”,CGLib是“类的代理”。
原理分析
- JDK Proxy 动态代理的实现无需引用第三方类,只需要实现
InvocationHandler
接口,重写invoke()
方法即可
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.");
}
}
static class JDKProxy implements InvocationHandler {
private Object target; // 代理对象
// 获取到代理对象
public Object getInstance(Object target) {
this.target = target;
// 取得代理对象
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
/**
* 触发代理的执行方法
* @param proxy 代理对象
* @param method 代理方法
* @param args 方法的参数
*/
@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.
//动态代理的使用
//new 一个目标对象
Taxi taxi = new Taxi();
//new一个InvocationHandler代理器(自己实现)
JDKProxy jdkProxy = new JDKProxy();
//将目标对象作为参数传入创建代理对象的方法
Object obj = jdkProxy.getInstance(taxi);
//强转为目标对象的接口类型
Taxi newTaxi = (Taxi)obj;
推荐一篇博客解释invoke()
如何自动调用的?点击访问
- CGLib的实现
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 在初始化被代理类时,是通过 Enhancer 对象把代理对象设置为被代理类的子类来实现动态代理的,因此被代理类不能被关键字 final 修饰。
- 原理:生成一个继承B的类型C(代理类),这个代理类持有一个MethodInterceptor,我们setCallback时传入的。 C重写所有B中的方法(方法名一致),然后在C中,构建名叫“CGLIB”+“$父类方法名$”的方法(下面叫cglib方法,所有非private的方法都会被构建),方法体里只有一句话super.方法名(),可以简单的认为保持了对父类方法的一个引用,方便调用,重写方法则是外界调用的入口。