zoukankan      html  css  js  c++  java
  • 代理模式

    代理模式

      为其他对象提供一个代理以控制对某个对象的访问。代理类主要负责为委托了(真实对象)预处理消息、过滤消息、传递消息给委托类,代理类不现实具体服务,而是利用委托类来完成服务,并将执行结果封装处理。代理类本身不实现服务,而是通过调用被代理类中的方法来提供服务。

      从设计模式上看,代理模式与适配器模式中的对象适配器很像。通过持有一个被代理对象的引用,对其方法进行包装,实际执行的还是被代理对象中的方法。可以让我们在符合开闭原则的前提下去扩展一个已经存在的类。

      从实现上看,代理模式因为语言层面提供的支持,我们有更加丰富的实现方式。除了传统的包装外,我们还可以通过 JDK 原生提供的 Proxy 或 CGLIB 等字节码技术工具实现动态代理。

    静态代理

      创建一个接口,然后创建被代理的类实现该接口并且实现该接口中的抽象方法。之后再创建一个代理类,同时使其也实现这个接口。在代理类中持有一个被代理对象的引用,而后在代理类方法中调用该对象的方法。

      被代理类接口:

    public interface Animal {
        public void sayHellow();
    }

      被代理类:

    public class Dog implements Animal {
        @Override
        public void sayHellow() {
            System.out.println("wang wang");
        }
    }

      代理类:

    public class DogProxy implements Animal {
        private Dog dog = new Dog();
        @Override
        public void sayHellow() {
            System.out.println("this is a dog");
            dog.sayHellow();
        }
    }

      执行结果,代理后的前置处理生效:

      使用静态代理很容易就完成了对一个类的代理操作。但是静态代理的缺点也暴露了出来:由于代理只能为一个类服务,如果需要代理的类很多,那么就需要编写大量的代理类,比较繁琐。遇到这种情况,我们可以使用动态代理的方式,无论要代理的类是什么,都可以生成继承自相同接口的代理类,并在其中实现代理逻辑。

    动态代理

      我们以使用 JDK 自带的动态代理实现为例。

      核心方法为 Proxy.newProxyInstance 方法,该方法需要传入 被代理类的类加载器、被代理类实现的接口 以及一个 InvocationHandler 的实现类实例。

      方法会通过字节码技术为我们返回一个代理对象,代理对象会实现被代理对象的所有接口,并在这些接口声明方法前后织入 InvocationHandler 中的代理逻辑。

      我们首先定义一个 InvocationHandler 实现类,写入代理逻辑:

    public class ProxyHandler implements InvocationHandler {
    
        private Object object;
    
        ProxyHandler(Object object) {
            this.object = object;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("before invoke");
            method.invoke(object, args);
            System.out.println("after invoke");
            return null;
        }
    }

      然后使用 Proxy.newProxyInstance 方法获得代理类对象:

        public static void main(String[] args) {
            Dog dog = new Dog();
            InvocationHandler handler = new ProxyHandler(dog);
            Animal dogProxy = (Animal) Proxy.newProxyInstance(Dog.class.getClassLoader(), Dog.class.getInterfaces(), handler);
            dogProxy.sayHellow();
    
            System.out.println(dogProxy.getClass().getName());
            System.out.println(dogProxy.getClass().toString());
        }

      执行结果,代理对象中织入了代理逻辑:

     动态代理的原理

    • 通过实现 InvocationHandler 接口创建自己的调用处理器;
    • 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
    • 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
    • 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
      动态代理并没有什么黑魔法,前面我们讲过字节码文件的 JVM 规范。动态代理便是按照规范新生成了一个 继承 Proxy 并实现了传入接口列表的类的字节码文件,并加载进了 JVM 。
      生成的代理类中有所有传入接口列表中的接口所包含的方法,其固定实现便是调用我们的 InvocationHandler 实现类中的 invoke。如果觉着抽象我们可以直接分析生成的代理类的字节码文件,首先设置全局变量 sun.misc.ProxyGenerator.saveGeneratedFiles 为 true,该参数控制生成的字节码文件是否保存,默认为 false。
        public static void main(String[] args) {
            System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
    
            Dog dog = new Dog();
            InvocationHandler handler = new ProxyHandler(dog);
            Animal dogProxy = (Animal) Proxy.newProxyInstance(Dog.class.getClassLoader(), Dog.class.getInterfaces(), handler);
            dogProxy.sayHellow();
        }

      然后运行,便可以看到新生成的代理类的字节码文件:

       我们反编译一下该文件,我将需要注意的点放在注释中:

    // 继承了 Proxy 并实现了 Animal
    public
    final class $Proxy0 extends Proxy implements Animal {
       //生成字节码文件时,为其设置的 Object 中的方法
    private static Method m1; private static Method m3; private static Method m2; private static Method m0;    // 构造函数,需要传入一个 InvocationHandler 实现类实例,也就是我们实现的处理器实例 public $Proxy0(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } }
       //我们要代理的方法
    public final void sayHellow() throws { try {
            //调用了我们传人的 InvocationHandler 实现类实例的 invoke,所以我们的代理逻辑都是写在 invoke 中的
    super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final int hashCode() throws { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } }    //生成字节码文件时,为其绑定的 Object 中的方法 static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m3 = Class.forName("proxy.Animal").getMethod("sayHellow"); m2 = Class.forName("java.lang.Object").getMethod("toString"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }

      可能部分童鞋会对 super.h.invoke(this, m3, (Object[])null); 这一句表示疑问,如何调用的被代理类的方法。

      我们看生成的静态语句块中:

       在代理类被加载时,m3 成员变量便已经被赋值为被代理类中的 sayHellow 方法的 Method 对象,所以 invoke 时只需传入 m3 即可。

       我觉着动态代理技术更适合叫动态生成类的字节码技术,因为整个执行过程的核心在于按照上述逻辑生成一个继承 Proxy 并实现了传入接口列表中接口的新类的字节码文件。至于是否对某个对象进行代理使我们在事项 InvocationHandler 时写在 invoke 方法中的。我们完全可以在 invoke 方法中不执行被代理类的方法,固定的做一些其它的事情。比如 Dubbo 框架中生成代理对象,invoke 方法便是进行了一次远程调用,而不是执行传入代理类对象的对应方法。

  • 相关阅读:
    Nginx系列教材 (四)- 和Tomcat进行动静分离整合
    Nginx系列教材 (三)- 反响代理Tomcat
    Nginx系列教材 (二)- 为Nginx准备的多个Tomcat
    Nginx系列教材 (一)- 教程
    Redis系列教材 (六)- Client
    Redis系列教材 (五)- Spring Data Redis 使用例子
    Redis系列教材 (四)- Jedis 教程
    Redis系列教材 (三)- 常见命令
    jq实时监听input值变化
    rem适配的代码
  • 原文地址:https://www.cnblogs.com/niuyourou/p/12531942.html
Copyright © 2011-2022 走看看