你了解Java中的动态代理的作用是什么吗?动态代理又有哪些实现方式,真正的实现原理是什么呢? 掌握本章内容,在面试遇到aop如何实现的问题时,就不会只知道jdk代理接口和cglib集成子类、不知道真正的实现原理了。
本文将会讲解动态代理技术能够解决的问题、如何自己设计实现动态代理、以及Java中是如何实现的以及一些开源框架中的典型应用。
dynamic proxy是什么,可以用来做什么?
通过dynamic proxy动态代理,我们可以在运行为接口创建实现类,或为特定类进行功能增强(aop功能)。常用在框架实现中,在retrofit、spring、dubbo中都大量使用了动态代理技术,框架并不能提前知道用户编写的类是什么样的,而通过动态代理,可以在运行时进行实现接口或增强用户类。
举一些实际的例子。 例如在retrofit(一种Http调用框架)中,用户通过下面的方式来定义和使用远程http接口。
publicinterfaceGitHubService{@GET("users/{user}/repos")Call<List<Repo>>listRepos(@Path("user")Stringuser);}
先定义远程的http的接口形式,通过注解标注http method, url, 参数,返回对象等。然后就可以利用Retrofit类传入这个接口获得对象进行调用了。
Retrofitretrofit=newRetrofit.Builder().baseUrl("https://api.github.com/").build();GitHubServiceservice=retrofit.create(GitHubService.class);
背后就是使用了动态代理技术来生成这个接口类的实现,在用户调用listRepos方法的时候,封装成http请求进行调用,并把结果反序列化成接口的返回值返回给用户,用户不需要感知这些实现细节,大大减少了使用成本。
如果不用retrofit proxy,需要怎么实现呢?
List<Repo>listRepos(Stringuser)throwsException{Stringurl=String.format("https://api.github.com/users/%s/repos",user);OkHttpClientclient=newOkHttpClient().newBuilder().build();Requestrequest=newRequest.Builder().url(url).method("GET",null).build();Responseresponse=client.newCall(request).execute();Stringbody=response.body().string();returnJSONUtils.jsonToModel(body,List.class);}
不用retrofit直接用okhttp开发的话,就需要使用者手动拼url、把请求参数构建成Map,调用okhttp接口后,手动把string body反序列化转成业务类对象。 这样就需要使用者了解底层实现和调用方式,这些额外的细节信息增加了使用成本。使用动态代理则可以屏蔽这些可以由框架实现的细节,让使用者面向接口编程,把更多的精力放在实现业务功能而不是底层细节上。
Java中动态代理的不同实现方式
我们来实现动态代理,该如何实现
在Java生态中有多种实现动态代理的方式,在介绍各个实现方案之前,我们不妨想一下,如果我们要来实现这个动态代理功能该如何设计呢?
我们需要先明确动态代理要实现的功能,以为接口创建动态代理为例,动态代理工厂(Proxy工厂)要负责为用户指定的接口类(被Proxy目标类)创建实现类,并且能够让 用户传入指定的实现(InvocationHandler),最终在运行时得到一个这个接口类的实现类(Proxy结果类)的对象实例。
这里的关键点就是,明确我们要生成一个什么样的类,以及怎么在运行时生成这个类。
第一步先定义我们要生成的类的内容,我们要生成的类,需要实现用户定义的接口类,并且实现这个接口的所有方法。实现的方法体内,调用用户传入的InvocationHandler对象处理,并返回对应的结果。
例如我们把InvocationHandler定义为
interfaceInvocationHandler{Objectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable;}
用户定义的接口类(被Proxy类)为
interfaceService{Stringhello();}
那么我们要生成的代理类ProxyImpl可以是如下的类,构造函数接收InvocationHandler对象,然后在具体的方法中,转发给InvocationHnalder对象处理。
classProxyImplimplementsService{privatestaticMethodhelloMethod;static{try{helloMethod=Service.class.getDeclaredMethod("hello",int.class);}catch(NoSuchMethodExceptione){e.printStackTrace();}}privatefinalInvocationHandlerinvocationHandler;publicProxyImpl(InvocationHandlerinvocationHandler){this.invocationHandler=invocationHandler;}@OverridepublicStringhello(intarg){try{return(String)invocationHandler.invoke(this,helloMethod,newObject[]{arg});}catch(Throwablethrowable){thrownewRuntimeException(throwable);}}}
明确了要生成的类的内容,下一步就是该如何实现了。该怎么才能在运行时生成一个具体的类呢?幸好Java的类加载机制提供了这种可能,Java中具备在运行时加载类的能力,我们所需要做的就是在运行时生成这个类的class文件字节码。
class文件格式在Java虚拟机规范(https://docs.oracle.com/javase/specs/jvms/)中有的明确详细介绍,我们按照规范拼出对应的字节码就可以了。 自己拼字节码繁琐且容易出错(就像写机器码),我们可以利用asm、bytebuddy等字节码工具来提高效率减少错误(就像用汇编)。 除了拼字节码还有一种方式是拼好源代码然后使用Java编译器编译源代码到字节码,我们可以使用java compiler api或javassist来实现。
最终得到需要的类的字节码后,再使用classloader来进行加载就可以得到一个可以使用的class对象了,最后利用反射创建对应类的实例对象返回给用户。
Java中的不同动态代理的实现和以及各自优缺点
在Java生态中,大家常用的动态代理的方式有jdk proxy和cglib,这两种方式提供了直接的接口。javassist,asm是更底层通用的字节码层面操作工具,它们也能实现对等的功能。
jdk proxy的实现的详细分析
最后我们以jdk proxy为例,结合代码看一下具体的实现。
我们顺着jdk proxy的方法入口(java.lang.reflect.Proxy#newProxyInstance),可以看到Proxy先得到了Constructor,然后利用反射调用Constructor传入InvocationHandler参数得到了代理类实例。
getProxyConstructor方法中,调用了ProxyBuilder来构建Proxy代理类。
最终生成代理类的函数是java.lang.reflect.ProxyGenerator#generateClassFile。
generateClassFile做的事情就是我们在前面提到的按照class文件字节码格式要求拼出对应的字节码数组。
会针对每个proxy方法创建Method字段,创建静态代码块获取Method,然后创建各个Proxy类的方法的实现。
动态代理在开源框架中的应用
下面分析一下retrofit中对于动态代理的使用。 如本文最开始的示例,我们通过retrofit.create(GitHubService.class)就可以生成GitHubService这个接口的代理类的对象。
create方法的实现也非常简洁,classloader使用传入接口类的classloader,接口列表使用传入的接口类,invocationHandler的实现是通过method生成调用okhttp的逻辑来调用(对jdk8的default方法进行了特殊处理,因为default方法是用户的实现,不需要代理)。