美文网首页
静态代理与动态代理详解

静态代理与动态代理详解

作者: Android开发_Hua | 来源:发表于2020-10-11 23:04 被阅读0次

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

    知识点汇总:

    一:什么是代理模式

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

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

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

    五:扩展阅读

    一:什么是代理模式

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

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

    代理模式的优点:

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

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

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

    代理模式的结构:

    图解:

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

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

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

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

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

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

    基于接口的代理模式:

    描述:一个比较直观的方式,就是定义一个功能接口,然后让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文件就已经被创建了。

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

    图解:

    图解二:

    类加载详细流程:

    静态代理总结:

    优点:

    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()方法。

    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, "<init>", "()V", null, null);

               mv.visitCode();

               mv.visitVarInsn(Opcodes.ALOAD, 0);

               mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>","()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<List<Repo>> repos =service.listRepos("octocat");

    Retrofit源码实现:

    public<T> T create(final Class<T> 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);

                           }

                           ServiceMethod<Object, Object>serviceMethod=

                                   (ServiceMethod<Object, Object>)loadServiceMethod(method);

                           OkHttpCall<Object>okHttpCall= newOkHttpCall<>(serviceMethod,args);

                           returnserviceMethod.adapt(okHttpCall);

                       }

                   });

       }

    Retrofit动态代理实现流程图:

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

    自定义通信接口:

    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插件化原理解析——概要)

    相关文章

      网友评论

          本文标题:静态代理与动态代理详解

          本文链接:https://www.haomeiwen.com/subject/zwqpuktx.html