java之代理模式介绍


一.java代理模式?

------

一.java代理模式?

设计模式的分类25种:
总体来说设计模式分为三大类:
创建型模式,共5种:单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式。
结构型模式,共7种:代理模式、适配器模式、桥接模式、装饰器模式、外观模式、享元模式、组合模式。
行为型模式,共11种:观察者模式、策略模式、模板方法模式、责任链模式、迭代子模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
其实还有两类:并发型模式和线程池模式。

前言
反射有一条知识点就是可用于生成代理模式,先阅读反射知识,以助于更好的理解Java的代理模式。

简介:
什么是代理,Proxy(代理) ?
代理分为静态代理和动态代理。
静态代理是在编译时就将接口、实现类、代理类全部手动完成。
但如果需要很多的代理,每一个都手动的去创建实属浪费时间,而且会有大量的重复代码,此时可采用动态代理。
动态代理可以在程序运行期间根据需要动态的创建代理类及其实例,来完成具体的功能,主要用的是JAVA反射机制。

按照代理创建时期,代理类可以分为两种:
静态:由程序员创建代理类或特定工具自动生成源代码再对其编译。在程序运行前代理类的.class文件就已经存在了。
动态:在程序运行时运用反射机制动态创建而成。

其实方法直接调用就可以完成功能,为什么还要加个代理呢?
原因是采用代理模式可以有效的将具体的实现与调用方进行解耦,通过面向接口进行编码完全将具体的实现隐藏在内部。
Proxy代理模式是一种结构型设计模式,主要解决问题是:在直接访问对象时带来的问题。
ps:面试有可能会问到哦

代理是一种常用设计模式,其目的是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。
更通俗的说,代理解决的问题当两个类需要通信时,引入第三方代理类,将两个类的关系解耦,让我们只了解代理类即可,
而且代理的出现还可让我们完成与另一个类之间的关系的统一管理,但切记(代理类和委托类要实现相同的接口,因为代理真正调用的还是委托类的方法)。

静态代理:
实代理的一般模式就是静态代理的实现模式:首先创建一个接口(JDK代理都是面向接口的),然后创建具体实现类来实现这个接口,在创建一个代理类同样实现这个接口,不同指出在于,具体实现类的方法中需要将接口中定义的方法的业务逻辑功能实现,而代理类中的方法只要调用具体类中的对应方法即可,这样我们在需要使用接口中的某个方法的功能时直接调用代理类的方法即可,将具体的实现类隐藏在底层。

第一步:定义总接口Iuser.java
public interface Iuser {
void eat(String s);
}
第二步:创建具体实现类UserImpl.java
被代理人
public class UserImpl implements Iuser {
  @Override
  public void eat(String s) {
    System.out.println("我要吃"+s);
  }
}
第三步:创建代理类UserProxy.java
代理人
public class UserProxy implements Iuser {
  private Iuser user = new UserImpl();
  @Override
  public void eat(String s) {
    System.out.println("静态代理前置内容");
    user.eat(s);
    System.out.println("静态代理后置内容");
  }
}
第四步:创建测试类
public class ProxyTest {
  public static void main(String[] args) {
    UserProxy proxy = new UserProxy();
    proxy.eat("苹果");
  }
}
运行结果:
静态代理前置内容
我要吃苹果
静态代理后置内容
综上的代码和输出结果可以看出,静态代理实现方法还是很简单的。都需要实现总接口,代理人里面持有被代理人的对象。代理人可以根据情况的不同,添加一些操作。

静态代理类优缺点 ?
优点:
代理使客户端不需要知道实现类是什么,怎么做的,而客户端只需知道代理即可(解耦合)。
缺点:
1)代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
2)代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了

举例说明:代理可以对实现类进行统一的管理,如在调用具体实现类之前,需要打印日志等信息,这样我们只需要添加一个代理类,在代理类中添加打印日志的功能,然后调用实现类,这样就避免了修改具体实现类。满足我们所说的开闭原则。但是如果想让每个实现类都添加打印日志的功能的话,就需要添加多个代理类,以及代理类中各个方法都需要添加打印日志功能(如上的代理方法中删除,修改,以及查询都需要添加上打印日志的功能)
即静态代理类只能为特定的接口(Service)服务。如想要为多个接口服务则需要建立很多个代理类。

动态代理:
动态代理的思维模式与之前的一般模式是一样的,也是面向接口进行编码,创建代理类将具体类隐藏解耦,不同之处在于代理类的创建时机不同,动态代理需要在运行时因需实时创建。

动态代理优点
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。
这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。而且动态代理的应用使我们的类职责更加单一,复用性更强

总结:
其实所谓代理,就是一个人或者一个机构代表另一个人或者另一个机构采取行动。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之前起到中介的作用。
代理对象就是把被代理对象包装一层,在其内部做一些额外的工作,比如用户上facebook,而普通网络无法直接访问,网络代理帮助用户先FQ,然后再访问facebook。这就是代理的作用了。
纵观静态代理与动态代理,它们都能实现相同的功能,而我们看从静态代理到动态代理的这个过程,我们会发现其实动态代理只是对类做了进一步抽象和封装,使其复用性和易用性得到进一步提升而这不仅仅符合了面向对象的设计理念,其中还有AOP的身影,这也提供给我们对类抽象的一种参考。关于动态代理与AOP的关系,个人觉得AOP是一种思想,而动态代理是一种AOP思想的实现!

第一步:定义总接口
public interface Iuser {
  void eat(String s);
}
第二步:创建具体实现类
被代理人
public class UserImpl implements Iuser {
  @Override
  public void eat(String s) {
    System.out.println("我要吃"+s);
  }
}
第三步:创建实现InvocationHandler接口的代理类
代理人
//动态代理类只能代理接口(不支持抽象类),代理类都需要实现InvocationHandler类,实现invoke方法。
// 该invoke方法就是调用被代理接口的所有方法时需要调用的,该invoke方法返回的值是被代理接口的一个实现类
public class DynamicProxy implements InvocationHandler {
//用于接收具体实现类的实例对象
  private Object object;
  //使用带参数的构造器来传递具体实现类的对象
  public DynamicProxy(Object obj){
    this.object = obj;
  }
  @Override
  public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
    System.out.println("前置内容");
    method.invoke(object, args);
    System.out.println("后置内容");
    return null;
  }
}
第四步:创建测试类ProxyTest.java
public class ProxyTest {
  public static void main(String[] args) {
    Iuser user = new UserImpl();
    InvocationHandler h = new DynamicProxy(user);
    Iuser proxy = (Iuser) Proxy.newProxyInstance(Iuser.class.getClassLoader(), new Class[]{Iuser.class}, h);
    proxy.eat("苹果");
  }
}
运行结果为:
动态代理前置内容
我要吃苹果
动态代理后置内容

2.动态代理的实现过程?
a. 首先我要说的就是接口,为什么JDK的动态代理是基本接口实现的呢?
  因为通过使用接口指向实现类的实例的多态实现方式,可以有效的将具体的实现与调用之间解耦,便于后期修改与维护。
再具体说就是在代理类中创建一个私有成员变量(private修饰),使用接口来指向实现类的对象(纯种的多态体现,向上转型的体现),然后在该代理类中的方法中使用这个创建的实例来调用实现类中的相应方法来完成业务逻辑功能。
这么说起来,我之前说的“将具体实现类完全隐藏”就不怎么正确了,可以改成,将具体实现类的细节向调用方完全隐藏(调用方调用的是代理类中的方法,而不是实现类中的方法)。这就是面向接口编程,利用java多态特性,实现程序代码的解耦。

b. 创建代理类的过程
  动态代理的实现其实与静态代理类似,都需要创建代理类,但创建方式不同!
  不同之处体现在静态代理我们知根知底,知道要对哪个接口、哪个实现类来创建代理类,所以在编译前就直接实现与实现类相同的接口,直接在实现的方法中调用实现类中的相应(同名)方法即可;
而动态代理不同,我们不知它什么时候创建,也不知要创建针对哪个接口、实现类的代理类(因为它是在运行时因需实时创建的)。
  虽然二者创建时机不同,创建方式也不相同,但是原理是相同的。不同之处仅仅是:静态代理可以直接编码创建,而动态代理是利用反射机制来抽象出代理类的创建过程。

分析
1. 静态代理需要实现与实现类相同的接口,而动态代理需要实现的是固定的Java提供的内置接口(一种专门提供来创建动态代理的接口)InvocationHandler接口,因为java在接口中提供了一个可以被自动调用的方法invoke,这个之后再说。

2. 先看代码
private Object object;
  public UserProxy(Object obj){
this.object = obj;
}
  这几行代码与静态代理之中在代理类中定义的接口指向具体实现类的实例的代码异曲同工,通过这个构造器可以创建代理类的实例,创建的同时还能将具体实现类的实例与之绑定(object指的就是实现类的实例,这个实例需要在测试类中创建并作为参数来创建代理类的实例),实现了静态代理类中private Iuser user = new UserImpl();一行代码的作用相近,这里为什么不是相同,而是相近呢,主要就是因为静态代理的那句代码中包含的实现类的实例的创建,而动态代理中实现类的创建需要在测试类中完成,所以此处是相近。

3. invoke(Object proxy, Method method, Object[] args)方法,该方法是InvocationHandler接口中定义的唯一方法,该方法在调用指定的具体方法时会自动调用。 其参数为:代理实例、调用的方法、方法的参数列表
  在这个方法中我们定义了几乎和静态代理相同的内容,仅仅是在方法的调用上不同,不同的原因与之前分析的一样(创建时机的不同,创建的方式的不同,即反射),Method类是反射机制中一个重要的类,用于封装方法,该类中有一个方法那就是invoke(Object object,Object…args)方法,其参数分别表示:所调用方法所属的类的对象和方法的参数列表,这里的参数列表正是从测试类中传递到代理类中的invoke方法三个参数中最后一个参数(调用方法的参数列表)中,在传递到method的invoke方法中的第二个参数中的(此处有点啰嗦)。

4. 测试类中的异同
  静态代理中我们测试类中直接创建代理类的对象,使用代理类的对象来调用其方法即可,若是别的接口(这里指的是别的调用方)要调用Iuser的方法,也可以使用此法。
动态代理中要复杂的多,首先我们要将之前提到的实现类的实例创建(补充完整),然后利用这个实例作为参数,调用代理来的带参构造器来创建“代理类实例对象”,这里加引号的原因是因为它并不是真正的代理类的实例对象,而是创建真正代理类实例的一个参数,这个实现了InvocationHandler接口的类严格意义上来说并不是代理类,我们可以将其看作是创建代理类的必备中间环节,这是一个调用处理器,也就是处理方法调用的一个类,不是真正意义上的代理类,可以这么说:创建一个方法调用处理器实例。
  下面才是真正的代理类实例的创建,之前创建的”代理类实例对象“仅仅是一个参数
  Iuser proxy = (Iuser) Proxy.newProxyInstance(Iuser.class.getClassLoader(), new Class[]{Iuser.class}, h);
  这里使用了动态代理所依赖的第二个重要类Proxy,此处使用了其静态方法来创建一个代理实例,其参数分别是:类加载器(可为父类的类加载器)、接口数组、方法调用处理器实例。 这里同样使用了多态,使用接口指向代理类的实例,最后会用该实例来进行具体方法的调用即可。

拓展:
JAVA学习篇–静态代理VS动态代理 - 何静媛 - CSDN博客
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)

--远程代理?
  远程代理可以为一个位于不同的地址空间的对象(例如服务端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方法,先判断有没初始化,然后再返回。

动态代理简单的实现? 

/**
* 委托对象和代理对象实现的接口
*/
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 create(final Class 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);
}
});
}