博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JRE与cglib
阅读量:3933 次
发布时间:2019-05-23

本文共 12792 字,大约阅读时间需要 42 分钟。

在讨论动态代理前,我们先回想一下java的反射机制。因为动态代理是会用到反射的,而且java的反射不仅使用广泛,而且功能强大。

反射——JLR

当我们使用new关键字创建对象时,虚拟机会先查看对象所属的类是否被加载到内存中,如果没有,就会先通过类的全限定类名来加载,当加载并初始化完成后,再进行对象的创建工作。当一个类文件被类加载器加载到虚拟机中,虚拟机会读取文件信息,并生成一个该类的Class对象。因此,如果我们可以直接操作虚拟机堆中的Class对象,那么就不需要new来创建对象。这种思想就是反射。

反射支持的方法有多种,主要的有:

获取Class对象

  • Object.getClass()
  • Class.forName(className)
  • Object.class

对Class对象进行操作

  • 对CLass对象类名进行操作:

    • Class.getName:获取全限定类名
    • Class.getCanonicalName():获取全限定类名
    • Class.getSimpleName():获取简单类名
  • 对Class对象修饰符进行操作:

    • Class.getModifiers():获取Class对象修饰符
    • Class.isAbstract(int modifiers)
    • Class.isFinal(int modifiers)
    • Class.isInterface(int modifiers)
    • Class.isNative(int modifiers)
    • Class.isPrivate(int modifiers)
    • Class.isProtected(int modifiers)
    • Class.isPublic(int modifiers)
    • Class.isStatic(int modifiers)
    • Class.isStrict(int modifiers)
    • Class.isSynchronized(int modifiers)
    • Class.isTransient(int modifiers)
    • Class.isVolatile(int modifiers)
  • 对Class对象包进行操作:

    • Class.getPackage():获取包对象
    • PackageObject.getname():获取包名
  • 对CLass对象继承的类/实现的接口进行操作:

    • Class.getSuperClass():获取父类
    • Class.getInterface():获取接口
  • 对Class对象构造函数进行操作:

    • Class.getConstructors():获取无参构造函数对象
    • Class.getConstructor(new Class[]):返回有参构造函数对象(如果没有该构造函数,将抛出NoSuchMethodException 异常)
    • ConstructorObject.getParameterTypes():获取给定的构造函数接受什么参数。
    • ConstructorObject.newInstance(Object[]):实例化对象
  • 对Class方法进行操作:

    • Class.getMethods()/Class.getMethod(String, Object[]):获取类的方法对象数组
    • Class.getDeclaredMethod(String,Class[])/Class.getDeclaredMethods():返回私有方法对象
    • MethodObject.getParameterTypes():获取方法参数数组
    • MethodObject.getReturnType():获取方法返回值类型
    • MethodObject.invoke(Object, Object):第一个参数为执行该方法的对象(如果该方法是静态的,则传null值),第二个参数是参数数组。
  • 对Class字段进行操作:

    • Class.getFields()/Class.getField(String):获取Class字段数组
    • Class.getDeclaredField()/Class.getDeclaredFields():获取私有字段
    • FieldObject.getName():获取字段对象的名称
    • FieldObject.getType():获取字段对象的类型
    • FieldObject.get(Object)/FieldObject.set(Object, Object):获取/设置字段值

JLR动态代理

jdk的静态代理就不用说了,就是创建一个接口,然后让目标类和代理类都实现这个接口,并且它们都有一个接收代理接口的构造参数,然后用的时候就创建目标类,然后把目标类传入代理类,以后操作就用代理类就可以了。这样后期修改代理代码就只要修改代理类一处就好了。具体代码实现如下:

public class StaticProxy {
public static void main(String[] args) {
TargetClass targetClass = new TargetClass(); ProxyClass proxyClass = new ProxyClass(targetClass); proxyClass.doSomething(); }}interface Proxy{
public void doSomething();}class TargetClass implements Proxy{
@Override public void doSomething() {
System.out.println("我是目标类"); }}class ProxyClass implements Proxy{
private TargetClass targetClass; public ProxyClass(TargetClass targetClass) {
this.targetClass = targetClass; } @Override public void doSomething() {
System.out.println("我是代理类"); targetClass.doSomething(); }}

由以上代码我们可以看出静态代理的大体架构如下所示:1592095558052

静态代理的实现原理很简单,在代理类少的情况下可以这样做,但是当代理规模一上来,不仅要写诸多的代理类,而且要注意代理类之间的关系,可能有的代理类代理的是代理类的代理的代理类。。。(有一种用为所欲为成语接龙的感觉)。这个时候,就要隆重推出反射使用中最靓的仔——jdk动态代理。

jdk动态代理不止需要目标类和代理类,它还需要一个中间类来通过调用invoke()方法来执行目标类的方法,动态代理的大体架构如下所示:1592095588344

jdk动态代理的代码示例如下:

public class DynamicProoxyByJLR {
public static void main(String[] args) {
// 注意:旧版的jdk应该使用 //System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true"); TargetClass targetClass = new TargetClass(); Class
aClass = targetClass.getClass(); MyProxy myProxy = (MyProxy) Proxy.newProxyInstance(aClass.getClassLoader(), aClass.getInterfaces(), new ProxyClass(targetClass)); myProxy.doSomething(); }}interface MyProxy{
public void doSomething();}class TargetClass implements MyProxy{
@Override public void doSomething() {
System.out.println("我是目标类"); }}class ProxyClass implements InvocationHandler {
private MyProxy target; public ProxyClass(MyProxy target) {
this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我是动态代理中间类"); // 通过反射调用目标类的方法 method.invoke(target, args); return null; }}

当执行完该程序后,我们可以看到在项目的当前目录会生成一个新的文件夹,该文件夹中就有jdk动态代理生成的Proxy0.class文件(我的路径是项目路径/cn/bigkai/proxy),虚拟机就是加载这个Class文件来进行动态代理的。我们通过反编译器来看看这个Class文件的内容:

// 篇幅有限,删除了部分不重要的内容package cn.bigkai.proxy;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.lang.reflect.UndeclaredThrowableException;// 该类继承Proxy类,实现了MyProxy接口final class $Proxy0        extends Proxy        implements MyProxy {
private static Method m1; private static Method m3; private static Method m2; private static Method m0; static {
try {
// 通过反射获取方法对象,从下面可以看出不仅需要获取代理方法,而且要获取equals、toString、hashCode方法。 m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{
Class.forName("java.lang.Object")}); m3 = Class.forName("cn.bigkai.proxy.MyProxy").getMethod("doSomething", new Class[0]); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); return; } catch (NoSuchMethodException localNoSuchMethodException) {
throw new NoSuchMethodError(localNoSuchMethodException.getMessage()); } catch (ClassNotFoundException localClassNotFoundException) {
throw new NoClassDefFoundError(localClassNotFoundException.getMessage()); } } public $Proxy0(InvocationHandler paramInvocationHandler) throws {
// 将paramInvocationHandler传递给Proxy的h属性保存起来 super(paramInvocationHandler); } public final void doSomething() throws {
try {
// 这里调用$Proxy0(InvocationHandler paramInvocationHandler)传递给父类h的invoke方法 // this表示当前代理类,m3表示doSomething方法的全类名,null是方法的参数(没有参数就是null) this.h.invoke(this, m3, null); return; } catch (Error | RuntimeException localError) {
throw localError; } catch (Throwable localThrowable) {
throw new UndeclaredThrowableException(localThrowable); } }}

对以上代码分析我们可以看出,jdk的动态代理其实就是实现InvocationHandler的拦截器,通过反射机制生成代理接口的$Proxy0类,在调用具体方法前调用InvokeHandler来处理,传入指定的当前类、方法、方法参数来执行。

cglib

利用ASM框架,对代理对象类生成的class文件加载进来,通过修改其字节码生成子类来实现动态代理的框架。cglib不同于jdk 动态代理通过接口实现来实现,它是针对类实现代理的,主要是对指定的类生成一个子类,实现指定的方法增强,因为是采用继承的方式,所以对于final修饰的类或方法,是无法继承的。

下面我们先来看看cglib的入门实例:

public class DynamicProxyByCglib {
public static void main(String[] args) {
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\JAVACODE\\flink-demo"); // 创建Enhancer对象,相当于代理类的创建工厂 Enhancer enhancer = new Enhancer(); // 设置目标类的字节码文件 enhancer.setSuperclass(TargetClass.class); // 设置拦截器作为回调函数 enhancer.setCallback(new MyMethodInterceptor()); // 创建代理类 TargetClass proxyClass = (TargetClass)enhancer.create(); proxyClass.doSomething(); }}class TargetClass {
public void doSomething(){
System.out.println("我是目标类"); }}class MyMethodInterceptor implements MethodInterceptor{
@Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("我是动态代理中间类"); return methodProxy.invokeSuper(o, objects); }}

当执行完程序后,在指定目录下会出现这三个文件:1592099239351

我们主要来看一下TargetClass E n h a n c e r B y C G L I B EnhancerByCGLIB EnhancerByCGLIB99529e9文件,因为它是真正执行代理类方法的:

// 继承目标类TargetClass,实现Factory接口,Factory的作用是设置属性和工厂式创建实例化对象public class TargetClass$$EnhancerByCGLIB$$99529e9 extends TargetClass    implements Factory{
private boolean CGLIB$BOUND; public static Object CGLIB$FACTORY_DATA; private static final ThreadLocal CGLIB$THREAD_CALLBACKS; private static final Callback CGLIB$STATIC_CALLBACKS[]; private MethodInterceptor CGLIB$CALLBACK_0; private static final Object CGLIB$emptyArgs[]; private static Object CGLIB$CALLBACK_FILTER; // 一个方法对应一个Method类型,一个MethodProxy类型 private static final Method CGLIB$doSomething$0$Method; private static final MethodProxy CGLIB$doSomething$0$Proxy; private static final Method CGLIB$equals$1$Method; private static final MethodProxy CGLIB$equals$1$Proxy; private static final Method CGLIB$toString$2$Method; private static final MethodProxy CGLIB$toString$2$Proxy; private static final Method CGLIB$hashCode$3$Method; private static final MethodProxy CGLIB$hashCode$3$Proxy; private static final Method CGLIB$clone$4$Method; private static final MethodProxy CGLIB$clone$4$Proxy; static {
CGLIB$STATICHOOK1(); } public TargetClass$$EnhancerByCGLIB$$99529e9() {
CGLIB$BIND_CALLBACKS(this); } // 对静态属性赋值 static void CGLIB$STATICHOOK1() {
// 这两个数组用来保存反射获取的Method对象 Method amethod[]; Method amethod1[]; CGLIB$THREAD_CALLBACKS = new ThreadLocal(); CGLIB$emptyArgs = new Object[0]; // 获取目标类的字节码文件 Class class1 = Class.forName("cn.bigkai.proxy.TargetClass$$EnhancerByCGLIB$$99529e9"); // 代理类的字节码文件 Class class2; // ReflectUtils是一个包装各种反射操作的工具类,通过这个工具类来获取各个方法的Method对象,然后保存到上述的Method数组中 amethod = ReflectUtils.findMethods(new String[] {
"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;" }, (class2 = Class.forName("java.lang.Object")).getDeclaredMethods()); Method[] _tmp = amethod; // 为目标类的每一个方法都建立索引,方便查找(FastClass机制) CGLIB$equals$1$Method = amethod[0]; CGLIB$equals$1$Proxy = MethodProxy.create(class2, class1, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1"); CGLIB$toString$2$Method = amethod[1]; CGLIB$toString$2$Proxy = MethodProxy.create(class2, class1, "()Ljava/lang/String;", "toString", "CGLIB$toString$2"); CGLIB$hashCode$3$Method = amethod[2]; CGLIB$hashCode$3$Proxy = MethodProxy.create(class2, class1, "()I", "hashCode", "CGLIB$hashCode$3"); CGLIB$clone$4$Method = amethod[3]; CGLIB$clone$4$Proxy = MethodProxy.create(class2, class1, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4"); amethod1 = ReflectUtils.findMethods(new String[] {
"doSomething", "()V" }, (class2 = Class.forName("cn.bigkai.proxy.TargetClass")).getDeclaredMethods()); Method[] _tmp1 = amethod1; CGLIB$doSomething$0$Method = amethod1[0]; CGLIB$doSomething$0$Proxy = MethodProxy.create(class2, class1, "()V", "doSomething", "CGLIB$doSomething$0"); } final void CGLIB$doSomething$0() {
super.doSomething(); } public final void doSomething() {
CGLIB$CALLBACK_0; if(CGLIB$CALLBACK_0 != null) goto _L2; else goto _L1 // 如果方法拦截器Wie空,就返回_L1: JVM INSTR pop ; CGLIB$BIND_CALLBACKS(this); CGLIB$CALLBACK_0;_L2: JVM INSTR dup ; JVM INSTR ifnull 37; goto _L3 _L4_L3: break MISSING_BLOCK_LABEL_21;_L4: break MISSING_BLOCK_LABEL_37; this; CGLIB$doSomething$0$Method; CGLIB$emptyArgs; CGLIB$doSomething$0$Proxy; intercept(); // 执行拦截器方法 return; super.doSomething(); return; }}

FaskClass

FastClass机制:我们知道使用反射调用方法的效率比new慢,因此cglib提供了FastClass机制,该机制是直接new一个新的实例类,然后对该实例类的方法标记索引,在代理时通过索引直接调用对应的方法。FastClass调用方法如下所示:

public class DynamicProxyByCglib {
public static void main(String[] args) throws InvocationTargetException {
// FastClass创建子实例 FastClass fastClass = FastClass.create(TargetClass.class); // 创建目标类实例 TargetClass fastClassInterface = (TargetClass)fastClass.newInstance(new Class[]{
String.class}, new Object[]{
"zhangsan"}); // 调用委托类方法 fastClass.invoke("say", new Class[]{
String.class}, fastClassInterface, new Object[]{
"李四"}); }} class TargetClass {
private String name; public TargetClass() {
} public TargetClass(String name) {
this.name = name; } public void doSomething(){
System.out.println("我是目标类"); } public void say(String name){
System.out.println(name + " 你好,我是 " + this.name); }}

cglib 流程

当我们创建了Enhance对象,设置了相关属性并实例化了代理类后,调用代理类的doSomething方法,此时代码的执行流程如下:

  1. 进入方法拦截器,调用intercept方法。

  2. 调用intercept方法内的invokeSuper方法,进入invokeSuper代码内。

  3. 调用invokeSuper方法内的init方法,init方法为目标类的方法建立索引。

    init方法源码如下:1592103581564

    1592103650103

  4. 当调用init方法获取FastClass后,就调用代理类的方法执行:1592103817383

  5. 调用代理类的invoke方法执行,实际上就是调用了目标类的对应方法:1592103936068

总结

Cglib JLR
是否提供子类代理
是否提供接口代理 是(可强制)
jdk1.6及以下两者效率 比JLR要高 比cglib低
jdk1.6到jdk1.8两者效率 在调用次数多时高于JLR 在调用次数少时高于cglib
jdk1.8及以上两者消息 低于JLR 高于cglib
区别 通过继承类来实现代理,不能继承被final修饰的类和属性 实现InvocationHandler使用Proxy.newProxyInstance产生代理对象,调用invoke方法实现方法增强

转载地址:http://yjqgn.baihongyu.com/

你可能感兴趣的文章
udhcpc 移植和使用
查看>>
libnl3.2 移植
查看>>
Uboot 使用串口Kermit协议传输文件
查看>>
wpa_supplicant 移植和使用
查看>>
hostapd 移植和使用
查看>>
windows下go 环境安装
查看>>
ubuntu13.04源更新
查看>>
netbeans 应用
查看>>
php 编码及加密
查看>>
专注核心
查看>>
职业规划
查看>>
高效会议
查看>>
不一样的产品思维
查看>>
写给PM新人的一些建议
查看>>
sublime xdebug 配置
查看>>
sublime preference配置
查看>>
sublime安装与配置
查看>>
HTML5认知
查看>>
Ubuntu nginx 配置https
查看>>
Thrift 操作 Hbase
查看>>