静态代理与动态代理详解

        最近常常看到一些资料时,是不是会看到动态代理,但是在项目中却好像没怎么使用过动态代理,所以对动态代理的理解也大概只有一个概念,最近部门规定,每两周最好要有一次技术分享,所以就借着这个机会,好好梳理一下动态代理到底是什么东西,下面是今天需要了解的相关知识点,让我们由浅入深一步步的了解什么是代理模式吧。


知识点汇总:

一:什么是代理模式

二:代理模式的常见实现方式:基于接口的代理模式 和 基于类继承的代理模式

三:静态代理与动态代理的区别与实现

四:代理模式在Android中的使用场景

五:扩展阅读


一:什么是代理模式

简介:在Java编程里就有一种设计模式,即代理模式,提供了一种对目标对象的访问方式,即通过代理对象访问目标对象,代理对象是指具有与被代理对象相同的接口的类,客户端必须通过代理对象与被代理的目标类进行交互。

       代理模式主要分为三个角色:客户端,代理类,目标类;而代理类需要与目标类实现同一个接口,并在内部维护目标类的引用,进而执行目标类的接口方法,并实现在不改变目标类的情况下前拦截,后拦截等所需的业务功能。


代理模式的优点:

中间隔离:某些情况下,客户端不想或者不能直接引用一个目标对象,而代理类可以在客户端和目标类之前起到中介作用

开闭原则,扩展功能:代理类除了是客户类和目标类的中介,还可以通过给代理类增加额外的功能来扩展目标类的功能,这样我们只需要修改代理类而不需要再修改目标类,符合代码设计的开闭原则(对扩展开放,对修改关闭)。代理类主要负责为目标类预处理消息、过滤消息、把消息转发给目标类,以及事后对返回结果的处理等。

      代理类本身并不真正实现服务,而是同过调用目标类的相关方法,来提供特定的服务。真正的业务功能还是由目标类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的目标类。

代理模式的结构:

静态代理与动态代理详解_第1张图片

图解:

静态代理与动态代理详解_第2张图片

代理模式的分类:(大概了解一下就好)

远程代理:为不同地理的对象提供局域网代表对象。

虚拟代理:根据需要将资源消耗很大的对象进行延迟,真正需要的时候再创建。

安全代理:控制用户的访问权限。

智能代理:提供对目标对象额外的服务「使用最多的」。(静态代理和动态代理)


二:代理模式的常见实现方式:基于接口的代理模式 和 基于类继承的代理模式

基于接口的代理模式:

描述:一个比较直观的方式,就是定义一个功能接口,然后让Proxy和RealSubject来实现这个接口。

定义接口:

public interface ILogin {

voiduserLogin();

}

定义目标类:(被代理类)

public class UserLogin implements ILogin {

@Override

public void userLogin() {

System.out.print("用户登录");

   }

}

定义代理类:

public classUserLoginProxyimplementsILogin{

privateUserLoginmLogin;

   publicUserLoginProxy() {

       mLogin= newUserLogin();

   }

   @Override

   public voiduserLogin() {

       System.out.print("登录前。。。");

       mLogin.userLogin();

       System.out.print("登录后。。。");

   }

}

客户端:

public class Test {

   public static void main(String[]args){

       ILoginloginProxy= newUserLoginProxy();

       loginProxy.userLogin();

   }

}


基于类继承的代理模式:

描述:还有比较隐晦的方式,就是通过继承,因为如果Proxy继承自RealSubject,这样Proxy则拥有了RealSubject的功能,Proxy还可以通过重写RealSubject中的方法,来实现多态。

//定义目标类:(被代理类)

   public classRealSubject{

       public voiduserLogin() {

       }

   }

   //定义代理类

   public class Proxy extendsRealSubject{

       @Override

       public voiduserLogin() {

           //todo:添加代理类代码

           super.userLogin();

           //todo:添加代理类代码

       }

   }


三:静态代理与动态代理的区别与实现

代理实现方式:如果按照代理创建的时期来进行分类的话, 可以分为静态代理、动态代理。

一:静态代理是由程序员创建或特定工具自动生成代理类,再对其编译,在程序运行之前,代理类.class文件就已经被创建了。

二:动态代理是在程序运行时通过反射机制动态创建代理对象。

图解:

静态代理与动态代理详解_第3张图片

图解二:

静态代理与动态代理详解_第4张图片

类加载详细流程:

静态代理与动态代理详解_第5张图片

静态代理总结:

优点:

1、在符合开闭原则的情况下,对目标对象功能进行扩展和拦截。

2、在访问无法访问的资源,增强现有的接口业务功能方面有很大的优点。

3、一个代理类只能代理一个真实的对象。

缺点:

1、需要为每个目标类创建代理类和接口,导致类的数量大大增加,工作量大,导致系统结构比较臃肿和松散。

2、接口功能一旦修改,代理类和目标类也得相应修改,不易维护。


动态代理实现:

技术实现:

简述:动态代理是java设计模式中代理模式的另一种实现,通过JVM在运行期通过反射为委托的接口类动态的生成代理的一种技术。

目前动态代理技术主要分为:

1、Java自己提供的JDK动态代理技术(只能实现基于接口的代理模式)

2、CGLIB技术(Android中不能使用,使用原理相似技术:ASM和Javaasis)

一:基于接口的代理模式(动态实现)

  在动态代理中,不需要我们再手动创建代理类,只需要编写一个动态处理器及指定要代理的目标对象实现的接口,真正的代理对象由JDK在运行时为我们创建;JDK提供了java.lang.reflect.InvocationHandler和java.lang.reflect.Proxy来实现动态代理。

简述:Proxy.getProxyClass(ClassLoader, interfaces)方法只需要接收一个类加载器和一组接口就可以返回一个代理Class对象,然后就可以通过反射创建代理实例,其原理就是从传入的接口Class中,拷贝类结构信息到一个新的Class对象中,并继承Proxy类,拥有构造方法;站在我们的角度就是通过接口Class对象创建代理类Class对象。

代码实现:

方法一:代理类对象生成代码

public static ObjectloadProxy(Object target) throws Exception {

       //通过接口Class对象创建代理Class对象

       Class proxyClass=Proxy.getProxyClass(target.getClass().getClassLoader(),target.getClass().getInterfaces());

       //拿到代理Class对象的有参构造方法

       Constructor constructors =proxyClass.getConstructor(InvocationHandler.class);

       //反射创建代理实例

       Object proxy =constructors.newInstance(newInvocationHandler() {

           @Override

           public Object invoke(Object proxy, Methodmethod, Object[]args) throwsThrowable{

               System.out.println("执行前日志..."+"\n");

               //执行目标类的方法

               Object result =method.invoke(target,args);

               System.out.println("执行后日志..."+"\n");

               return result;

           }

       });

       return proxy;

   }

客户端调用:

public static void main(String[]args) throws Exception {

       ILoginproxy = (ILogin)loadProxy(newUserLogin());

       proxy.userLogin();

   }

方法二:代理类对象生成代码(与retrofit类似)

Proxy类还有个更简单的方法newProxyInstance,直接返回代理对象,如下:

   public static ObjectloadProxy(Object object) {

       returnProxy.newProxyInstance(

             object.getClass().getClassLoader(), //和目标对象的类加载器保持一致

             object.getClass().getInterfaces(), //目标对象实现的接口,因为需要根据接口动态生成代理对象

               newInvocationHandler() { //事件处理器,即对目标对象方法的执行

           @Override

           publicObject invoke(Object proxy, Methodmethod, Object[]args) throwsThrowable{

                       System.out.println("执行前日志...");

                       Object result =method.invoke(object,args);

                       System.out.println("执行后日志...");

                       return result;

                   }

               });

   }

代理类:(动态生成基于接口的代理类代码)

public final class $Proxy0 extends Proxy implementsILogin

{

   public $Proxy0(InvocationHandlerinvocationhandler){

       super(invocationhandler);

   }

   public finalbooleanequals(Objectobj){

       try{

           return ((Boolean)super.h.invoke(this, m1, new Object[] {

               obj

           })).booleanValue();

       }

       catch(Error _ex) { }

       catch(Throwablethrowable){

           throw newUndeclaredThrowableException(throwable);

       }

   }   

 public final StringtoString(){

       try{

           return (String)super.h.invoke(this, m2, null);

       }

       catch(Error _ex) { }

       catch(Throwablethrowable){

           throw newUndeclaredThrowableException(throwable);

       }

   }

   public final voiduserLogin(){

       try{

           super.h.invoke(this, m3, null);

           return;

       }

       catch(Error _ex) { }

       catch(Throwablethrowable){

           throw newUndeclaredThrowableException(throwable);}

   }

public finalinthashCode(){

       try{

           return ((Integer)super.h.invoke(this, m0, null)).intValue();

       }

       catch(Error _ex) { }

       catch(Throwablethrowable){

           throw newUndeclaredThrowableException(throwable);

       }

   }

   private static Method m1;

   private static Method m2;

   private static Method m3;

   private static Method m0;

}

static{

       try{

           m1 =Class.forName("java.lang.Object").getMethod("equals", new Class[] {

               Class.forName("java.lang.Object")

           });

           m2 =Class.forName("java.lang.Object").getMethod("toString", new Class[0]);

           m3 =Class.forName("com.mango.demo.proxy.ILogin").getMethod("userLogin", new Class[0]);

           m0 =Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);

       }

       catch(NoSuchMethodExceptionnosuchmethodexception){

           throw newNoSuchMethodError(nosuchmethodexception.getMessage());

       }

       catch(ClassNotFoundExceptionclassnotfoundexception){

           throw newNoClassDefFoundError(classnotfoundexception.getMessage());

       }

   }

生成代理类解析:

1、JDK为我们生成了一个$Proxy0(这个名字后面的0是编号,有多个代理类会一次递增)的代理类。

2、代理类实现了ILogin接口,继承了Proxy类,并有一个带InvocationHandler参数的构造方法,使用super调用Proxy类的构造方法,证实了上面的分析。

3、实现了接口的userLogin方法,并在其内部调用InvocationHandler的invoke方法,其h正是Proxy类定义的成员变量。

4、最下面是通过反射拿到类中的几个方法,作为参数传递到InvocationHandler.invoke方法中,即调用动态代理对象的任何方法,最终都是走到InvocationHandler.invoke方法中(所以在invoke方法中写日志需要判断下,是否是调用代理对象指定的方法走到这里)。


JDK主要会做以下工作:

1、获取 RealSubject上的所有接口列表。

2、确定要生成的代理类的类名,默认为:com.sun.proxy.$ProxyXXXX。

3、根据需要实现的接口信息,在代码中动态创建该Proxy类的字节码。

4、将对应的字节码转换为对应的class对象。

5、创建InvocationHandler实例handler,用来处理Proxy所有方法调用。

6、Proxy的class对象以创建的handler对象为参数,实例化一个proxy对象。

仔细观察可以看出生成的动态代理类有以下特点:(并非上图代码demo)

1、继承自java.lang.reflect.Proxy,实现了Rechargable,Vehicle这两个ElectricCar实现的接口;

2、类中的所有方法都是final的;

3、所有的方法功能的实现都统一调用了InvocationHandler的invoke()方法。

静态代理与动态代理详解_第6张图片

JDK动态代理总结:(基于接口的代理模式)

优点:

1、相对于静态代理,极大的减少类的数量,降低工作量,减少对业务接口的依赖,降低耦合,便于后期维护;

2、同时在某些情况下是最大的优势,即可以统一修改代理类的方法逻辑,而不需要像静态代理需要修改每个代理类。

3、动态代理中所谓的“动态”,是针对使用Java代码实际编写了代理类的“静态”代理而言的,它的主要优势不在于省去了编写代理类那一点工作量,而是实现了可以在原始类和接口还未知的时候,就确定代理类的代理行为,当代理类与原始类脱离直接联系后,就可以很灵活地重用于不同的应用场景之中。(深入理解Java虚拟机)

缺点:

1、因为使用的是反射,所以在运行时会消耗一定的性能;

2、同时JDK代理只支持interface的动态代理,如果你再继续深究源码,会发现,所有动态生成的代理对象都有一个共同的父类,即都继承于Proxy;

3、Java的单继承机制决定了无法支持class的动态代理,也就意味着你拿到动态生成的代理对象,只能调用其实现的接口里的方法,无法像静态代理中的代理类可以在内部扩展更多的功能。


二:基于类的代理类实现(动态实现)

简述:JDK动态代理是实现AOP编程的一种途径,可以在执行指定方法前后贴入自己的逻辑,像Spring、Struts2就有用到该技术(拦截器设计就是基于AOP的思想),只不过JDK动态代理只能实现基于接口的动态代理,也算是一个遗憾,还有这种实现方式也不能避免类数量增加,因为你必须要为每个业务类编写业务接口。

提问:那么有没有不用写代理类、也不用写业务接口的代理方法呢?

解答:使用CGLib了,CGLIB(CodeGeneration Library)是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。

      CGLIB比JDK的代理更加强大,不只可以实现接口,还可以扩展类,通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势植入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理。

       CGLIB底层封装了ASM,通过对字节码的操作来生成类,具有更高的性能,但是CGLIB创建代理对象时所花费的时间却比JDK多;ASM是一套JAVA字节码生成架构,能够动态生成.class文件并在加载进内存之前进行修改。

     使用CGLIB需要引用jar包cglib-nodep-3.2.5.jar(如果引入cglib.jar,还需要引入asm的jar包)。

      但是在Android中的字节码生成技术,一般使用:ASM和Javassist。


Java字节码生成开源框架介绍--ASM:

简述:ASM 是一个 Java 字节码操控框架。它能够以二进制形式修改已有类或者动态生成类。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。

       不过ASM在创建class字节码的过程中,操纵的级别是底层JVM的汇编指令级别,这要求ASM使用者要对class组织结构和JVM汇编指令有一定的了解。

       使用ASM框架提供了ClassWriter接口,通过访问者模式进行动态创建class字节码,看下面的例子:

ASM动态生成代码:

public static void main(String[]args) throwsIOException{

           System.out.println();

           ClassWriterclassWriter= newClassWriter(0);

           //通过visit方法确定类的头部信息

           classWriter.visit(Opcodes.V1_7,// java版本

                   Opcodes.ACC_PUBLIC,//类修饰符

                   "Programmer", //类的全限定名

                   null, "java/lang/Object", null);

           //创建构造函数

           MethodVisitormv =classWriter.visitMethod(Opcodes.ACC_PUBLIC, "", "()V", null, null);

           mv.visitCode();

           mv.visitVarInsn(Opcodes.ALOAD, 0);

           mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "","()V");

           mv.visitInsn(Opcodes.RETURN);

           mv.visitMaxs(1, 1);

           mv.visitEnd();

}

ASM动态生成代码:(合并上面代码)         

          //定义code方法

           MethodVisitormethodVisitor=classWriter.visitMethod(Opcodes.ACC_PUBLIC, "code", "()V",

                   null, null);

           methodVisitor.visitCode();

           methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",

                   "Ljava/io/PrintStream;");

           methodVisitor.visitLdcInsn("I'm aProgrammer,JustCoding.....");

           methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println",

                   "(Ljava/lang/String;)V");

           methodVisitor.visitInsn(Opcodes.RETURN);

           methodVisitor.visitMaxs(2, 2);

           methodVisitor.visitEnd();

           classWriter.visitEnd();

           //使classWriter类已经完成

           //将classWriter转换成字节数组写到文件里面去

           byte[] data =classWriter.toByteArray();

           Filefile= new File("D://Programmer.class");

           FileOutputStreamfout= newFileOutputStream(file);

           fout.write(data);

           fout.close();


Java字节码生成开源框架介绍--Javassist:

简述:Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba(千叶滋)所创建的。它已加入了开放源代码JBoss应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态AOP框架。javassist是jboss的一个子项目,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。

Javassist动态生成代码:

publicstatic void main(String[]args) throws Exception {

           ClassPoolpool =ClassPool.getDefault();

           //创建Programmer类 

           CtClasscc=pool.makeClass("com.samples.Programmer");

           //定义code方法

           CtMethodmethod =CtNewMethod.make("public void code(){}", cc);

           //插入方法代码

           method.insertBefore("System.out.println(\"I'm aProgrammer,JustCoding.....\");");

           cc.addMethod(method);

           //保存生成的字节码

           cc.writeFile("d://temp");

       }


四:代理模式在Android中的使用场景

一:Retrofit中的动态代理

二:AIDL实现进程之间的通信(底层使用Binder实现)

三:Android的插件化实现原理之--hook机制


一:Retrofit中的动态代理

调用代码:GitHubServiceservice =retrofit.create(GitHubService.class);

                     Call> repos =service.listRepos("octocat");

Retrofit源码实现:

public T create(final Class service) {

       Utils.validateServiceInterface(service);

       if (validateEagerly) {

           eagerlyValidateMethods(service);

       }

       return (T)Proxy.newProxyInstance(service.getClassLoader(), new Class[] { service },

               newInvocationHandler() {

                   private final Platformplatform=Platform.get();

                   @Override public Object invoke(Object proxy, Methodmethod, @NullableObject[]args)

                           throwsThrowable{

                       // If the method is a method from Object then defer to normal invocation.

                       if (method.getDeclaringClass() ==Object.class) {

                           returnmethod.invoke(this,args);

                       }

                       if (platform.isDefaultMethod(method)) {

                           returnplatform.invokeDefaultMethod(method, service, proxy,args);

                       }

                       ServiceMethodserviceMethod=

                               (ServiceMethod)loadServiceMethod(method);

                       OkHttpCallokHttpCall= newOkHttpCall<>(serviceMethod,args);

                       returnserviceMethod.adapt(okHttpCall);

                   }

               });

   }

Retrofit动态代理实现流程图:

静态代理与动态代理详解_第7张图片

二:AIDL实现进程之间的通信(底层使用Binder实现)

静态代理与动态代理详解_第8张图片

自定义通信接口:

interfaceIMyAidlInterface{

       intgetBookCount();

       voidaddBook(String book,intprice);

   }

生成的代理类代码:(去除部分代码,仅保留关键代码)

publicinterfaceIMyAidlInterfaceextendsandroid.os.IInterface{

   public static abstract class Stub extendsandroid.os.Binderimplementscom.jerey.learning.IMyAidlInterface{

       private static finaljava.lang.StringDESCRIPTOR = "com.jerey.learning.IMyAidlInterface";

       publicStub(){

           this.attachInterface(this, DESCRIPTOR);

       }

       public staticcom.jerey.learning.IMyAidlInterfaceasInterface(android.os.IBinderobj) {

       }

       @Override publicandroid.os.IBinderasBinder(){

           return this;

       }

       @Override publicbooleanonTransact(intcode,android.os.Parceldata,android.os.Parcelreply,intflags) throwsandroid.os.

       RemoteException{

           returnsuper.onTransact(code, data, reply, flags);

       }

        //多么熟悉的Proxy

       private static class Proxy implementscom.jerey.learning.IMyAidlInterface{

           privateandroid.os.IBindermRemote;

           Proxy(android.os.IBinderremote){

               mRemote= remote; 

}

@Override publicandroid.os.IBinderasBinder(){

               returnmRemote;

}

           publicjava.lang.StringgetInterfaceDescriptor(){

               return DESCRIPTOR;

           }

           @Override publicintgetBookCount() throwsandroid.os.RemoteException{ //.........................

           }

           @Override public voidaddBook(java.lang.Stringbook,intprice) throwsandroid.os.RemoteException{ //........................

           }

       }

       static finalintTRANSACTION_getBookCount= (android.os.IBinder.FIRST_CALL_TRANSACTION+ 0);

       static finalintTRANSACTION_addBook= (android.os.IBinder.FIRST_CALL_TRANSACTION+ 1);

   }

   publicintgetBookCount() throwsandroid.os.RemoteException;

   public voidaddBook(java.lang.Stringbook,intprice) throwsandroid.os.RemoteException;

}

附加:生成的代理类请参考网址:https://www.jianshu.com/p/5646b9b7b898


三:Android的插件化实现原理之--hook机制

解析:调用采用了动态代理的办法,如果我们自己创建代理对象,然后把原始对象替换为我们的代理对象,那么就可以在这个代理对象为所欲为了,修改参数,替换返回值,我们称之为Hook。

   首先我们得找到被Hook的对象,我称之为Hook点;什么样的对象比较好Hook呢?自然是容易找到的对象。什么样的对象容易找到?

1、静态变量和单例,在一个进程之内,静态变量和单例变量是相对不容易发生变化的,因此非常容易定位,而普通的对象则要么无法标志,要么容易改变。

2、尽量Hookpulic的对象和方法,非public不保证每个版本都一样,需要适配。

  找到了Hook点之后,这个hook点就是一个被代理对象,我们就可以动态的生成代理类,创建代理类对象,并通过反射获取被代理对象,然后把被代理对象替换成我们的代理类对象,从而达到扩展被代理对象的功能,甚至完全修改相关操作行为。  


五:扩展阅读

1、https://blog.csdn.net/qq_30993595/article/details/90796869(Android开发如何理解Java静态代理 动态代理及动态生成代理对象原理 看这篇就够了)

2、https://www.jianshu.com/p/64d205a159e6(一次Android权限删除经历)

3、https://blog.csdn.net/sinat_23092639/article/details/102237404(从动态代理角度看Retrofit)

4、https://www.jianshu.com/p/7068295be51a(Android中使用Java的动态代理)

5、http://weishu.me/2016/01/28/understand-plugin-framework-proxy-hook/(Android插件化原理解析——Hook机制之动态代理)

6、https://www.jianshu.com/p/08203d371f1c(将cglib动态代理思想带入Android开发)

7、https://www.jianshu.com/p/e709aff78a53(Java动态代理机制详解)

8、https://segmentfault.com/a/1190000012278673(人人都会设计模式:代理模式--Proxy)

9、https://blog.csdn.net/yingpaixiaochuan/article/details/85232965(动态代理在Retrofit中的使用)

10、https://www.ibm.com/developerworks/cn/java/j-lo-proxy1/index.html(Java动态代理机制分析及扩展,第1部分)

11、http://weishu.me/2016/01/28/understand-plugin-framework-proxy-hook/(Android插件化原理解析——Hook机制之动态代理)

12、http://weishu.me/2016/01/28/understand-plugin-framework-overview/(Android插件化原理解析——概要)

你可能感兴趣的:(静态代理与动态代理详解)