zoukankan      html  css  js  c++  java
  • java之动态代理

    --代理模式(反射&远程代理 ):
    代理模式概念? 为另一个对象提供一个替身 或 占位符以控制对这个对象的访问。 核心是反射。
    在Android中很多基于Binder机制的系统服务如ActivityManagerService、自己定义的AIDL都使用了远程代理来进行跨进程通信,如果使用过网络请求框架Retrofit,也使用了动态代理。 
      为什么要为一对象提供“替身”,或者去控制对对象的访问呢?直接访问对象不也是可以吗?
      现实是有些时候没有那么简单,例如如果客户端需要访问的对象在服务端,客户端直接访问需要处理网络等一系列复杂的问题,如果使用代理模式那么客户端只需要和代理打交道,客户端不需要知道代理怎么和服务端交互。当然这只是一个例子,在生活中也有例如火车票飞机票代售点这类代理思想的例子。
    我们就从最简单的代理模式说起吧!
    静态代理 ?
      代理类是在编译时就生成的,也就是说代理类在编译完成后已经存在.class文件。
    一般来说它的UML(直接使用Idea生成的,不是很美观)是这样的:
    RealSubject:原对象也可以称之为委托对象的类。
     SubjectProxy:代理对象类
     Subject:委托和代理对象类共同实现的接口,request()是其拥有的方法。 一般来说,代理模式都会包括上述的三个部分。只不过,会出现各种变体罢了。

    动态代理(依靠反射)?
    就是 在运行时才会生成代理类,和静态代理不同在编译阶段不会生成实在.class文件,而是在运行时动态生成类字节码。
    然后再通过ClassLoader加载到JVM中,简而言之,代理类是在运行时根据需要代理的接口等一系列参数动态生成的。
    Subject,RealSubject上文中已经说过了,这里着重介绍下边两个类:
    java.lang.reflect.Proxy:这个是生成代理类的核心类,位于java.lang.reflect包下,我们大致可以确定该类和反射有关。
    事实也确实如此,动态代理就是依靠反射生成代理类的,并且生成的代理类都是继承自Proxy,即$ProxyN extends Proxy。
    关于生成的代理类所在包和命名也有讲究的:
      1.如果所代理的接口访问权限是public,那么代理类会被定义在root包路径下,
    如果是package(java中接口不能被定义为private和protect)那么代理类会被定义在接口所在的包。
    这样做可以有效的防止生成的代理类出现包访问权限的问题。
      2.生成的代理类名称都是自动生成的类似”$ProxyN”格式的,N会随着代理类的数量增加,可以保证代理类的命名是唯一的。   
      3.生成的代理类都被final修饰符修饰,保证代理类不允许被继承。
      上文说了代理类会继承自Proxy,同时还会实现其代理的接口,这样做的目的是可以将代理类安全的类型转换成所代理的接口类型。
    代理类完整的继承关系:java.lang.reflect.InvocationHandler接口:该接口有一个invoke()方法,我们在使用动态代理时一般都需要自定义一个类然后实现该接口,自定义的类就是我们的调用处理器,我们可以在这里实现我们需要的任务,当运行时代理类调用任何方法都会回调invoke()方法。

    一般来说,使用Java动态代理的步骤?
      1.定义委托类和公共接口
      2.自定义一个实现了InvocationHandler接口的调用处理类(代码中的ProxyHandler),然后在invoke方法中实现自己想要的任务,例如监控方法的调用。
      3.通过Proxy.newProxyInstance方法生成代理类,代理对象。
      第3步中的Proxy.newProxyInstance方法的实现比较复杂,打算单独一篇文章分析,
    这里简单介绍一下。主要是看一下其参数:
    Object newProxyInstance(ClassLoader loader,Class<?>[]interfaces,InvocationHandler h)

    动态代理简单的实现? 

    /**
    * 委托对象和代理对象实现的接口
    */
    public interface Subject {
    void request();
    }

    /**
    * s
    * 委托类,该类真正实现了request()
    */
    public class RealSubject implements Subject {
    @Override
    public void request() {
    System.out.println("RealSubject::request");
    }
    }

    /**
    * 实现InvocationHandler
    */
    public class ProxyHandler implements InvocationHandler {
    private Subject subject;

    public ProxyHandler() {
    subject = new RealSubject();
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println("ProxyHandler:preInvoke");
    Object result = method.invoke(subject, args);
    System.out.println("ProxyHandler:finishInvoke");
    return result;
    }
    }

    public class ProxyTest {
    public static void main(String[] args) {
    ProxyHandler handler = new ProxyHandler();
    Subject proxy = (Subject) Proxy.newProxyInstance(RealSubject.class.getClassLoader()
    , RealSubject.class.getInterfaces(), handler);
    proxy.request();
    }
    }

      ClassLoader loader:类加载器,主要是将生成的代理类的字节码装载到JVM,动态生成的代理类同样需要ClassLoader加载,并为其指明对象。
    所以每次在生成代理类和代理类对象时,都需要指定一个类加载对象。 

     public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
    eagerlyValidateMethods(service);
    }
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
    new InvocationHandler() {
    private final Platform platform = Platform.get();

    @Override public Object invoke(Object proxy, Method method, Object... args)throws Throwable {
    // If the method is a method from Object then defer to normal invocation.
    if (method.getDeclaringClass() == Object.class) {
    return method.invoke(this, args);
    }
    if (platform.isDefaultMethod(method)) {
    return platform.invokeDefaultMethod(method, service, proxy, args);
    }
    ServiceMethod serviceMethod = loadServiceMethod(method);
    OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
    return serviceMethod.callAdapter.adapt(okHttpCall);
    }
    });
    }



    ---远程代理?
      远程代理可以为一个位于不同的地址空间的对象(例如服务端server)提供一个本地代理对象(存根或者桩)。这个不同的地址空间在java中可以是不同的进程甚至是不同的机器。
    远程代理可以将网络或者跨进程通信的细节隐藏起来,使得客户端不必考虑网络或跨进程的存在。客户完全可以认为被代理的对象是本地的而不是远程的,而代理对象承担了大部份的通信工作。
    上图只是概念图,实际要复杂的多,图中proxy即为server在客户端本地的代理对象。这样client可以直接和proxy交互,然后proxy处理和server的通信,他们之间的通信可以很复杂也可以简单。
    在java中可以有类似RMI一样的机制,在Android中也有其特殊的Binder机制。并且Android中大量使用其作为IPC通信方式。
    接下来我就结合AIDL来认识一下吧。 Binder作为Android系统IPC重要的通信方式,融合了代理模式和RMI机制的思想。
    在Android中的很多系统服务中如ActivityManagerService,PackageManagerService等都有很多应用,当然还有我们平时进行跨进程通信的AIDL也是以Binder为基础的。
    如果对AIDL还不熟悉可以先看一下从AIDL看Android跨进程通信 。AIDL在我们定义好接口后,会自动给我们生成一个对应的java文件,代码如下(直接用的从AIDL看Android跨进程通信的例子。):
    总结:代理还有其他多种变种,例如虚拟代理,Copy-on-Write代理,保护代理,防火墙(Firewall)代理,智能引用(Smart Reference)代理等。 总之,代理模式不管是在java,Android甚至是现实生活中都普通存在的模式,有必要掌握其思想。

    --代理模式?
    1设计原则:体现功能复用
    1常用场景:需要修改或屏蔽某一个或若干个类的部分功能,复用另外一部分功能,可使用静态代理,
    若是需要拦截一批类中的某些方法,在方法的前后插入一些一致的操作,假设这些类有一致的接口,可使用JDK的动态代理,否则可使用cglib
    1使用概率:99.99999%
    1复杂度:中高
    1变化点:静态代理没有变化点,动态代理的变化点为具有相同切入点的类
    1选择关键点:静态代理选择的关键点是是否要复用被代理的部分功能,动态代理选择的关键点在于能否在将被代理的这一批类当中,找出相同的切入点
    1逆鳞:切入点的不稳定
    1相关设计模式
    1适配器模式:对于适配器模式当中的定制适配器,它与静态代理有着相似的部分,二者都有复用功能的作用,不同的是,静态代理会修改一部分原有的功能,而适配器往往是全部复用,而且在复用的同时,适配器还会将复用的类适配一个接口。

    动态代理?AOP面向切面编程?
    简而言之,动态代理就是拦截调用的那个方法,在方法前后来做一些操作。
    Retrofit里的动态代理比较巧妙。实际上它根本就没有delegate。因为这个方法没有真正的实现。
    使用动态代理,只是单纯的为了拿到这个method上所有的注解。
    所有的工作都是由proxy做了。比起我们总说代理就是打log要高明多了。

    我以前自己写数据库框架时,也碰到这样的场景。一个类里有很多一对一,一对多关系。如果从db里fetch出来都去做初始化,那会非常影响性能。
    但如果不初始化,到使用时再去手动初始化就更麻烦了。怎么办呢?
    当类A里的get方法被invoke时,我就判断,这个类有没有被初始化,如果有,那就不做任何操作。如果没有,那得等会,
    我把数据从数据库中fetch出来给你赋值后,再去invoke。这个场景可以叫懒加载,可以套用AOP面向切面编程。

    动态代理能实现这个需求吗?
    可以,但是支持的很糟糕。因为动态代理依赖接口实现,总不能将所有的pojo中的方法都申明到接口里吧?那真是要命了。
    所以我用了种替代方案,既然是AOP,有个面向切面的框架AspectJ。你可以通过它来切入这些get方法,先判断有没初始化,然后再返回。

  • 相关阅读:
    天轰穿C# vs2010 04面向对象的编程之隐藏基类方法【原创】
    学云网助阵软博会 云教育平台备受关注
    天轰穿C# vs2010 04面向对象的编程之多态【原创】
    编程可以如此简单 学云网校园技术之旅
    天轰穿C# vs2010 04面向对象的编程之重载运算符【原创】
    天轰穿C# vs2010 04面向对象的编程之虚成员和重写【原创】
    天轰穿C# vs2010 04面向对象的编程之抽象类和抽象方法【原创】
    直播:1996年—2012年,哥从农民到清华大学出书的奋斗史
    天轰穿C# vs2010 04面向对象的编程之运算符重载的示例【原创】
    .天轰穿C# vs2010 04面向对象的编程之接口 VS 抽象类 【原创】
  • 原文地址:https://www.cnblogs.com/awkflf11/p/12628526.html
Copyright © 2011-2022 走看看