最近常常看到一些资料时,是不是会看到动态代理,但是在项目中却好像没怎么使用过动态代理,所以对动态代理的理解也大概只有一个概念,最近部门规定,每两周最好要有一次技术分享,所以就借着这个机会,好好梳理一下动态代理到底是什么东西,下面是今天需要了解的相关知识点,让我们由浅入深一步步的了解什么是代理模式吧。
知识点汇总:
一:什么是代理模式
二:代理模式的常见实现方式:基于接口的代理模式 和 基于类继承的代理模式
三:静态代理与动态代理的区别与实现
四:代理模式在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插件化原理解析——概要)