Java动态代理机制及原理
代理模式Java动态代理Java动态代理机制Java动态代理注意点Java动态代理测试创建一个动态代理类ProxyTestclass文件分析在运行期生成二进制字节码ASMJavassist源码分析Proxy类newProxyInstancegetProxyClass0方法分析InvocationHandler解析JDK动态代理机制CGLIB动态代理机制
代理模式
Java动态代理运用了设计模式中常用的代理模式代理模式:目的就是为其他对象提供一个代理用来控制对某个真实对象的访问代理类的作用:为委托类预处理消息过滤消息并转发消息进行消息被委托类执行后的后续处理通过代理层这一中间层,有效的控制对于真实委托类对象的直接访问,同时又可以实现自定义的控制策略,比如Spring中的AOP机制,这样使得在设计上获得更大的灵活性代理的基本构成:代理模式中有Subject角色,RealSubject角色和Proxy角色:Subject:负责定义RealSubject和Proxy角色应该实现的接口RealSubject:用来真正完成业务服务功能Proxy:负责将自身的Request请求,调用RealSubject对应的request功能实现业务功能,自身不做真正的业务静态代理模式:当在代码阶段规定这种代理关系,Proxy类通过编译器编译成class文件,当系统运行时,此class已经存在这种静态代理模式可以访问无法访问的资源,增强现有的接口业务功能方面有很大的优点.但是大量地使用这种静态代理,会使系统内的类规模增大,并且不易维护由于Proxy和RealSubject的功能本质上是相同的,Proxy只是中介的作用,这种代理在系统中的存在,会造成代码冗余为了解决静态代理模式的问题,就有了动态创建Proxy:在运行状态中,需要代理的地方,根据Subject和RealSubject,动态地创建一个ProxyProxy使用完之后,就会销毁,这样就可以避免Proxy角色的class在系统中的冗余问题Java动态代理
java.lang.reflect.Proxy:Java动态代理机制的主类提供一组静态方法为一组接口动态的生成对象和代理类java.lang.reflect.InvocationHandler:调用处理器接口,自定义invoke方法用于实现对真正委托类的代理访问java.lang.ClassLoader:类装载器将类的字节码装载到Java虚拟机即JVM中,并为其定义类对象,然后该类才能被使用Proxy类与普通类的唯一区别就是:Proxy类字节码是由JVM在运行时动态生成的而不是预存在于任何一个.calss文件中每次生成动态代理类对象时都需要指定一个类装载器对象
Java动态代理机制
Java动态代理创建对象的过程:
通过实现InvocationHandler接口创建自己的调用处理器通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理类通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入为了简化对象创建过程,Proxy类中使用newInstanceProxy封装了步骤2-步骤4,因此只需要两个步骤即可完成代理对象的创建
Java动态代理注意点
包:代理接口是public,则代理类被定义在顶层包,package为空,否则default,代理类被定义在该接口所在的包生成的代理类为publicfinal,不能被继承类名的格式为:$ProxyNN是逐一递增的数字,代表Proxy是被第N次动态代理生成的代理类对于同一组接口,接口的排列顺序也相同,不会重复创建动态代理类,而是返回一个先前已经创建并缓存了的代理类对象,以此提高效率类继承关系:Proxy类是父类,这个规则适用于所有由Proxy创建的动态代理类(这也导致Java动态代理的缺陷,由于Java不支持多继承,所以无法实现对class的动态代理,只能对于Interface进行代理),该类实现了所有代理的一组接口,所以Proxy类能够被安全地类型转换到其所代理的某接口代理类的根类java.lang.Object中的hashCode(),equals()和().toString方法同样会被分派到调用处理器invoke方法执行bind方法:bind方法中的newProxyInstance方法,就是生成一个代理对象第一个参数:类加载器第二个参数:真实委托对象所实现的接口.代理对象挂在那个接口下第三个参数:this代表当前HelloServiceProxy类,也就是使用HelloServiceProxy作为对象的代理invoke方法:invoke方法有三个参数:第一个proxy是代理对象第二个是当前调用那个方法第三个是方法的参数class文件分析
Java编译器编译好Java文件后,产生.class文件在磁盘中:class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码JVM虚拟机读取字节码文件,取出二进制数据加载到内存中,解析.class文件内的信息,生成对应的Class对象加载class文件字节码到系统内,转换成class对象,然后再实例化:定义一个类自定义一个类加载器,用于将字节码转换成class对象编译.class文件,在程序中读取字节码,然后转换成相应的class对象,再实例化在运行期生成二进制字节码
在代码中,动态创建一个类:由于JVM通过字节码的二进制信息加载类,因此在运行期的系统中,遵循Java编译系统组织.class文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成对应的类可以使用开源框架在运行时期按照Java虚拟机规范对class文件的组织规则生成对应的二进制字节码.比如ASM,JavassistASM
ASM是一个Java字节码操控框架:能够以二进制形式修改已有类或者动态生成类ASM在创建class字节码的过程中,操纵的级别是底层JVM汇编指令级别ASM可以直接产生二进制class文件,也可以在类被加载入Java虚拟机之前动态改变类行为ASM从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户的要求生成新类通过ASM生成类的字节码:使用ASM框架提供的ClassWriter接口,通过访问者模式进行动态创建class字节码然后使用Java反编译工具(JD_GUI)打开硬盘上生成的类.class文件查看类信息再使用定义的类加载器将class文件加载到内存中,然后创建class对象,并且实例化一个对象,调用code方法,查看code方法中的结果至此表明:在代码里生成字节码,并动态地加载成class对象,创建实例是完全可以实现的Javassist
Javassist是一个开源的分析,编辑和创建Java字节码的类库,已经加入JBoss应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态AOP框架:Javassist是JBoss一个子项目,主要优点在于简单快速直接使用Java编码的形式,不需要虚拟机指令,就能改变类的结构或者动态生成类
源码分析
动态代理的真正的关键是在getProxyClass0()方法getProxyClass0方法分析
通过getProxyClass0方法中生成具体的class文件的过程:定义path将class文件写到指定的硬盘中反编译生成的class文件getProxyClass0()方法分为四个步骤:
对这组接口进行一定程度的安全检查:1.1接口类对象是否对类装载器可见1.2接口类对象与类装载器所识别的接口类对象完全相同1.3确保是interface类型而不是class类型.从loaderToCache映射表中获取以类装载器对象为关键字所对应的缓存表,如果不存在,就会创建一个新的缓存表并更新到loaderToCahe中:2.1loaderToCache存放键值对:接口名字列表:动态生成的代理类的类对象的引用2.2当代理类正在被创建时,会临时进行保存:接口名字列表:pendingGenerationMarker2.3标记pendingGenerationMarker的作用是通知后续的同类请求(接口数组相同且组内接口排列顺序也相同)代理类正在被创建,请保持等待直至创建完成动态创建代理类的class对象首先根据接口public与否的规则生成代理类的名称-$ProxyN格式,然后动态生成代理类.所有的代码生成工作由ProxyGenerator完成,该类在rt.jar中,需要进行反编译
代码生成过程进入结尾部分,根据结果更新缓存表.如果代理类成功生成则将代理类的类对象引用更新进缓存表,否则清除缓存表中对应的关键值,最后唤醒所有可能的正在等待的线程InvocationHandler解析
Proxy角色在执行代理业务的时候,就是在调用真正业务之前或者之后完成一些额外的功能代理类就是在调用真实角色的方法之前或者之后做一些额外的业务为了构造具有通用性和简单性的代理类,可以将所有的触发真实角色动作交给一个触发管理器,让这个管理器统一管理触发,这个触发管理器就是InvocationHandler动态代理工作的基本工作模式:将方法功能的实现交给InvocationHandler角色外接对Proxy角色中每一个的调用,Proxy角色都会交给InvocationHandler来处理InvocationHandler则调用具体对象角色的方法在这种模式中,代理Proxy和RealSubject应该实现相同的类的public方法,有两种方式:一个比较直观的方式:就是定义一个功能接口,然后让Proxy和RealSubject来实现这个接口(JDK中的动态代理机制-Java动态代理机制)比较隐晦的方式:通过继承实现Proxy继承RealSubject.因为Proxy继承自RealSubject,这样Proxy则拥有了RealSubject的功能,Proxy还可以通过重写RealSubject中的方法来实现多态(cglib)JDK动态代理机制
JDK动态代理机制通过接口为RealSubject创建一个动态代理对象:获取RealSubject上的所有接口列表确定要生成的代理类类名根据需要实现的接口信息,在代码中动态创建该Proxy类的字节码将对应的字节码转换成对应的class对象创建InvocationHandler,用来处理Proxy所有方法调用Proxy的class对象,以创建的handler为参数,实例化一个proxy生成动态代理类的字节码并且保存到硬盘中生成的动态代理类的特点:继承自java.lang.reflect.Proxy,实现了Rechargable,Vehicle这两个ElectricCar接口类中的所有方法都是final的所有的方法功能的实现都统一调用了InvocationHandler的invoke()方法CGLIB动态代理机制
CGLIB通过类继承生成动态代理类JDK动态代理类的特点:某个类必须有实现的接口,而生成的代理类只能代理某个类接口定义的方法.这样会导致子类实现继承两个接口的方法外,另外实现的方法,在产生的动态代理类中不会有这个方法如果某个类没有实现接口,那么这个类就不能使用JDK动态代理了
CGLIB:CodeGenerationLibrary,CGLIB是一个强大的,高性能,高质量的Code生成类库,可以在运行时期扩展Java类与实现Java接口CGLIB创建类的动态代理类的模式:查找类中所有非final的public类型的方法定义将这些方法的定义转换成字节码将组成的字节码转换成相应的代理的class对象实现MethodInterceptor接口,用来处理对代理类上的所有方法的请求(类似JDK动态代理中的InvocationHandler的作用)通过CGLIB生成的class文件的内容