动态代理

42次阅读

共计 4626 个字符,预计需要花费 12 分钟才能阅读完成。

代理模式

代理模式 (Proxy) 就是为一个对象创建一个替身,用来控制对当前对象的访问,目的就是为了在不直接操作对象的前提下对对象进行访问。

为什么要用代理模式?

  • 中介隔离作用:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
  • 开闭原则,增加功能:代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。

代理模式主要分为两种:

  • 静态代理:在运行前,就确定好代理类、被代理类之间的关系
  • 动态代理:在运行时动态的创建一个代理类,实现一个或多个接口,将方法的调用转发到指定的类

静态代理

第一步:创建服务类接口

public interface BuyHouse {void buyHouse();
}

第二步:实现服务类接口

public class BuyHouseImpl implements BuyHouse {public void buyHouse() {System.out.println("我要买房");
    }
}

第三部:创建代理类

public class BuyHouseProxy implements BuyHouse {

    private BuyHouse buyHouse;

    public BuyHouseProxy(BuyHouse buyHouse) {this.buyHouse = buyHouse;}

    public void buyHouse() {System.out.println("买房前的准备");
        buyHouse.buyHouse();
        System.out.println("买房后的装修");
    }
}

第四步:测试

public class ProxyTest {public static void main(String[] args) {BuyHouse buyHouse = new BuyHouseImpl();
        buyHouse.buyHouse();
        BuyHouse buyHouseProxy = new BuyHouseProxy(buyHouse);
        buyHouseProxy.buyHouse();}
}

静态代理有两个弱点:

  1. 代理对象的一个接口只服务于一种类型的对象,要为每个服务创建代理类,程序规模稍大,静态代理就无法胜任了
  2. 若接口增加方法,除了每个实现类需要实现这个方法,所有的代理类也都要实现这个方法,增加代码维护的复杂度

于是乎到了”动态代理“上场的时候了

动态代理

JDK 动态代理

JDK 动态代理主要依赖反射,并依赖 JDK 中的 java.lang.reflect.Proxy、java.lang.ClassLoader、java.lang.reflect.InvocationHandler。

实现动态代理主要是下面几个步骤:

  1. 实现 InvocationHandler 接口,创建自己的调用处理器
  2. 给 Proxy 类提供代理类的 ClassLoader 和代理接口类型创建动态代理类
  3. 以调用处理器的 Class 对象为参数,利用反射机制得到动态代理类的构造函数
  4. 以调用处理器对象为参数,利用动态代理类的构造函数创建动态代理类对象

下面同样以买房来个示例:
首先实现自己的调用处理器:

public class MyInvocationHandler implements InvocationHandler {

    private Object obj;

    public MyInvocationHandler(Object obj) {this.obj = obj;}

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("买房前的准备");
        method.invoke(obj, args);
        System.out.println("买房后的装修");
        return null;
    }
}

测试类:

public class DynamicProxyTest {public static void main(String[] args) {
            // 保存生成的代理类的字节码文件
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        
        BuyHouse buyHouse = new BuyHouseImpl();
        InvocationHandler invocationHandler = new MyInvocationHandler(buyHouse);
        Class clazz = buyHouse.getClass();
        BuyHouse buyHouseProxy = (BuyHouse)Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), invocationHandler);
        buyHouseProxy.buyHouse();}
}

从上可以看出主要是 Proxy.newProxyInstance 这个方法去生成代理类,现在来看看这个方法的实现

再看上图中的 getProxyClass0()方法,这个方法主要是生成代理类的字节码文件

直接看下 ProxyClassFactory 如何生成代理类

在测试类中加上 System.getProperties().put(“sun.misc.ProxyGenerator.saveGeneratedFiles”, “true”); 这段代码可以把生成的代理类字节码文件保存下来,如下

从上图可以看出:

  1. 代理类继承了 Proxy 接口,并且实现了要代理的接口,由于 Java 不支持多继承,所以 JDK 动态代理不能代理类
  2. 重写了 hashCode、equals、toString 方法
  3. 有个静态代码块,通过反射获取代理类的所有方法
  4. 通过 invoke 执行代理类中的目标方法 buyHouse

CGLIB 代理

使用 CGLIB 大致分为四个步骤:

  1. 创建被代理的对象
  2. 创建方法拦截器
  3. 创建代理对象
  4. 调用代理对象

如下示例:

首先是被代理类

public class BuyHouseService {public void buyHouse() {System.out.println("我要买房");
    }
}

然后是拦截器

public class BuyHouseServiceIntercepter implements MethodInterceptor {public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("买房前的准备");
        proxy.invokeSuper(obj, args);
        System.out.println("买房后的装修");
        return null;
    }
}

测试类

public class CglibTest {public static void main(String[] args) {System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"/Users/xiechao/myWork/learn/com/sun/cglib");
        //Enhancer 是生成代理类的工厂
        Enhancer enhancer = new Enhancer();
        // 设置代理的超类  即被代理的对象
        enhancer.setSuperclass(BuyHouseService.class);
        // 设置拦截方法
        enhancer.setCallback(new BuyHouseServiceIntercepter());
        // 生成代理对象
        BuyHouseService buyHouseService = (BuyHouseService)enhancer.create();
        buyHouseService.buyHouse();}
}

看下 CGLIB 的核心代码

fastClass 机制

JDK 动态代理使用反射调用目标对象的方法,在 CGLIB 中为了更好的提升性能,采用 fastClass 机制。fastClass 机制首先会将类的方法信息解析出来,然后为其建索引,调用的时候只要传索引,就能找到相应的方法进行调用。

  1. 为所有的方法建索引
  2. 调用前先根据方法信息得到索引
  3. 调用时根据索引匹配相应的方法

CGLIB 在字节码层面将方法和索引的关系建立,避免了反射调用,反编译后得到 getIndex 源代码如下:

上述方法获得索引,再利用索引根据下面的方法进行调用

反编译代理对象的源码前先看下代理调用的过程图:

从图中可以看出:

  1. 客户端调用代理对象的被代理方法
  2. 代理对象将调用委派给方法拦截器统一接口 intercepter
  3. 方法拦截器执行前置操作,然后调用方法代理的统一接口 invokeSuper
  4. 方法代理的 invokeSuper 初始化代理对象和被代理对象的 fastClass
  5. 初始化后再调用代理对象的 fastClass
  6. 代理对象的 fastClass 能够快速的找到代理对象的代理方法
  7. 代理对象的代理方法再调用被代理对象的被代理方法
  8. 执行 intercepter 的后置操作,方法结束

现在来看反编译后的代理对象的源代码,运行时实际上回生成三个 class,分别是:

  • 代理类
  • 代理类对应的 fastClass
  • 被代理类对应的 fastClass

生成的代理类如下:

可以看出代理类是继承了对象,这点与 JDK 不同,jdk 是实现了接口

下面是代理对象的代理方法

intercepter 方法中比较让人关注的是 CGLIB$buyHouse$0$Method 参数

这个参数是在代理类被加载时会执行其静态方法,创建 buyHouse 方法的代理方法

再点进 create 方法瞧一瞧

其中,c1 是被代理类,c2 是代理类,desc 是代理方法和备代理方法的参数信息,name1 是被代理方法名,name2 是代理方法名,该方法创建了代理方法

再来看下 intercepter 方法中调用 methodProxy 的 invokeSuper 方法

看下初始化方法 init()

初始化后就能拿到代理类和代理方法的索引,就能按照索引对代理方法进行调用

通过索引直接查到代理类的代理方法为 CGLIB$buyHouse$0

这样就能在该方法中调用被代理类的被代理方法

CGLIB 代理的大致流程就是这样

总结

CGLIB 代理和 JDK 代理的不同点:

  • JDK 动态代理必须实现接口,CGLIB 既支持对接口的代理也支持对对象的代理
  • CGLIB 使用 FastClass 机制快速调用被代理类,JDK 中使用反射调用被代理类,所以 CGLIB 性能上更有优势

共同点:

  • 都有统一接口,JDK 的是 InvocationHandler,CGLIB 是 MethodIntercepter
  • 生成的代理类其中方法都被 final 修饰

正文完
 0