• 动态代理之 JDK 动态代理


    动态代理

    动态代理源于设计模式中的代理模式,代理模式的主要作用就是使代理对象完成用户的请求,屏蔽用户对真实对象的访问。通过代理对象去访问目标对象来控制原对象的访问。

    代理模式的最典型的应用就是 Spring AOP。

    静态代理

    代理模式的实现有两种,静态代理动态代理,静态代理的代理类是需要程序员去写的,而动态代理的代理类是自动生成的。

    静态代理需要持有被代理对象的引用,通过这个引用去调用被代理对象的方法。

    我们来看一个静态代理的实例:

    首先定义一个接口,代理对象和被代理对象都需要实现这个接口。

    public interface IService{
      void sayHello();
    }
    

    被代理的对象:

    public class RealClass implements IService{
      @Override
      public void sayHello(){
        System.out.println("hello world.....");
      }
      
      public void doService(){
        System.out.println("doing service.....");
      }
    }
    

    真正的代理类需要做的事情:

    public class ProxyClass implements IService{
      //被代理对象的引用
      private RealClass realClass;
      
      public ProxyClass(RealClass realClass){
        this.realClass = realClass;
      }
      
      @Override
      public void sayHello(){
        System.out.println("i am proxy, prepare for saying hello...");
        realClass.sayHello();
        System.out.println("i am proxy, saying hello ending...");
      }
      
      public void doService(){
        System.out.println("i am proxy, prepare for saying hello...");
        realClass.doService();
        System.out.println("i am proxy, saying hello ending...");
      }
    }
    

    一般来说,代理类会选择直接继承被代理类所有的接口和父类以便于实现所有被代理类的方法。

    到这里其实静态代理就讲完了,也没有什么难点。但是动态代理不同于静态代理的点在于,代理类不用我们自己写。

    JDK 动态代理

    动态代理区别于静态代理的一点是,动态代理的代理类由虚拟机在运行时动态创建并于虚拟机卸载时清除。

    我们还用上面的 RealClass 类,看看 JDK 的动态代理是如何实现的。

    首先定义一个 Handler 类,它继承自 InvocationHandler。

    public class MyHandler implements InvocationHandler{
      //同样需要传入被代理类的引用,这里我们直接使用 Object 类型
      private Object realObject;
      
      public MyHandler(Object realObj){
        this.realObject = realObj;
      }
      
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Exception{
            System.out.println("proxy begainning ... ");
        		// 通过反射的方式,调用被代理对象的方法
        		Object result = method.invoke(realObj,args);
        		System.out.println("proxy ending....");
        		return result;
      }
    }
    

    代理类的生成以及调用过程:

    public static void main(String[] args){
      RealClass rc = new RealClass();
      MyHandler handler = new MyHandler(rc);
      Object obj = Proxy.newProxyInstance(rc.getClass().getClassLoader(),new Class[]{IService.class},handler);
    }
    
    1. 首先我们定义处理类,它继承了 InvocationHandler 并实现了 invoke 方法。这里面还是需要传入被代理对象的引用。
    2. InvocationHandler 的实现类里面通过反射来调用被代理兑现的方法,还可以加上一些新的功能。
    3. 通过 JDK 提供的 Proxy 类的 newProxyInstance 方法来构建代理类,里面需要传入三个参数,类加载器,被代理类的所有接口,第三个是我们自己定义的 InvocationHandler 的实现类。

    我们通过反编译来看看这个代理类的 Class 到底有什么不同:

    1. 生成的代理类的名字是很随意的,一个程序中如果有多个代理类药生成,【$PRroxy + 数字】就是它们的类名。
    2. 这个代理类继承了 Proxy 类并且实现了 IService 接口(之前如果指定多个,这里就会继承多个)。
    3. 代理类的构造器需要传入 InvocationHandler 类型的参数。并且将这个参数传递给了父类。这也是为什么所有代理类都必须使用 Proxy 作为父类的一个原因。

    我们继续看代理类下面的内容:

    1. 包裹在 static 里面的静态代码块很重要,通过反射获取了四个 Method。其中有三个是 Object 类的常用方法,也就是说代理类还会代理被代理对象从 Object 继承来的方法,还有一个是被代理类的接口的方法。

    1. 最后一部分我们看到的是,虚拟机根据静态初始化代码块所反射出来的所有代理方法,为他们生成代理的方法。
    2. 调用时需要从父类 Proxy 中取出构造实例化时存储的处理器类,并调用它的 invoke 方法。
    3. 第一个参数是当前代理类的实例(事实证明这个参数并没有什么用),第二个参数是 Method 方法实例,第三个参数是方法的形式参数集合,如果没有就是 null。

    总结

    1. 一个处理器类的定义是必不可少的,它的内部必须关联一个真实对象,即被代理类实例。
    2. 我们从外部调用代理类的任意一个方法,从反编译的源码我们知道,代理类方法会转而去调用处理器的 invoke 方法并传入方法签名和参数。

    缺陷

    我们需要注意到,虚拟机生成的代理类为了共用 Proxy 类中的 InvocationHandler 字段来存储自己的处理器类实例而继承了 Proxy 类。

    这里有个问题,说明代理类不能在继承其他类了。那么被代理类父类的方法自然就无法获取了,即代理类无法代理被代理类中父类的任何方法,只能代理接口中的方法,这也是我们常说的 JDK 动态代理必须实现接口的原因。

    RealClass 自己的方法 doService 也没有被代理,只有接口中的方法被代理了。所以说,JDK 的动态代理机制是单一的,它只能代理被代理类的接口集合中的方法。

    类中的非接口方法和父类中的方法是无法被代理的。

    不友好的返回值。

    public static void main(String[] args){
      RealClass rc = new RealClass();
      MyHandler handler = new MyHandler(rc);
      Object obj = Proxy.newProxyInstance(rc.getClass().getClassLoader(),new Class[]{IService.class},handler);
    }
    

    newProxyInstance 返回的是代理类 【Proxy0】的一个实例,但是它是以 Object 类型进行返回的,而你又不能把它强转成 【Proxy0】类型。因为编译期是不存在这个【Proxy0】类型的,所以一般只会强转为该代理类实现的接口之一。

    IService obj = (IService)Proxy.newProxyInstance(
            rc.getClass().getClassLoader(),
            new Class[]{IService.class},
            hanlder);
    obj.sayHello();
    

    现在问题又来了,加入我们被代理类实现了多个接口,那么你该强转为哪个接口类型哪?可能需要你多次进行转换,这样的设计相当不友好。

    下一篇我们将介绍一个广为各类框架使用的 CGLIB 动态代理库,它的底层基于字节码操作框架 ASM,不再依赖继承来实现,完美的解决了 JDK 的单一代理的不足。

  • 相关阅读:
    (转)Web自动化测试中的接口测试
    Redis在.net中的应用学习
    Redis学习第八课:Redis高级实用特性(一)
    Redis学习第七课:键值命令和服务器命令
    Redis学习第六课:Redis ZSet类型及操作
    先验概率 vs 后验概率
    cout格式化输出
    python练习linux下创建路径
    把“苹果中国首发”视为扬眉吐气是自卑的表现
    字符串处理算法(三)按指定位置交换字符串两部分的位置
  • 原文地址:https://www.cnblogs.com/paulwang92115/p/12173397.html
Copyright © 2020-2023  润新知