Java设计模式之代理模式(二)

一、CGLIB动态代理

JDK动态代理要求被代理的类必须实现接口,有很强的局限性,而CGLIB动态代理则不要求被代理类实现接口。简单的说,CGLIB会让生成的代理类继承被代理类,并在代理类中对代理方法进行强化处理(前置处理、后置处理等)。在CGLIB底层,其实是借助了ASM这个非常强大的Java字节码生成框架。Cglib无法代理被final修饰的方法。

cglib的原理是继承,cglib通过继承目标类,创建它的子类,在子类中重写父类中同名的方法,实现功能的修改。因为cglib是继承,重写方法,所以要求目标类不能是final的,方法也不能是final的。cglib的要求目标类比较宽松,只要能继承就可以了。cglib在很多的框架中使用,比如mybatis,spring框架中都有使用。

1、cglib动态代理的简单使用

使用CGLIB动态代理主要有以下几个步骤:

1.编写一个被代理类(也称委托类或目标类),无需实现任何接口;

2.自定义一个方法拦截器,实现MethodInterceptor接口,并重写intercept方法;

3.在intercept方法中调用MethodProxy类的invokeSuper方法(而不是调用invoke方法,因为invoke会引起死循环,导致堆栈内存溢出,具体原因在下面会详细分析。在invokeSuper方法中,底层实际上最终会调用被代理类中相应的被代理方法);

4.创建代理对象,并调用被代理类中的方法(实际上是调用代理对象中重写的代理方法)。

示例代码:

java 复制代码
package com.tx.study.others.proxy.cglibProxy;

/**
 * @Author: 倚天照海
 * @Description: 被代理类
 */
public class InfoDemo {
    //被代理方法(被final修饰的方法无法被代理)
    public void welcome (String person){
        System.out.println("welcome :" + person);
    }

}
java 复制代码
package com.tx.study.others.proxy.cglibProxy;

import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @Author: 倚天照海
 * @Description: 自定义方法拦截器,实现MethodInterceptor接口,并重写intercept方法
 */
public class MyMethodInterceptor implements MethodInterceptor {

    /**
     * 重写intercept方法
     *
     * @param o 代理对象
     * @param method 被代理方法对应的Method对象
     * @param objects 被代理方法的参数
     * @param methodProxy 方法代理对象
     * @return 被代理方法的返回值
     * @throws Throwable 可能会抛出的异常
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //在真实的对象执行之前可以添加自己的操作,插入前置通知
        System.out.println("before method!!!");
        //在invokeSuper方法中最终会调用被代理类中对应的被代理方法
        Object value = methodProxy.invokeSuper(o, objects);
        //会引起死循环,导致内存溢出
        //Object value = methodProxy.invoke(o, objects);
        //在真实的对象执行之后可以添加自己的操作,插入后置通知
        System.out.println("after method!!!");
        return value;
    }

}
java 复制代码
package com.tx.study.others.proxy.cglibProxy;

import org.springframework.cglib.core.DebuggingClassWriter;
import org.springframework.cglib.proxy.Enhancer;

/**
 * @Author: 倚天照海
 * @Description: cglib动态代理测试类
 */
public class CglibProxyTest {
    /**
     * 该方法的作用就是封装获取代理对象的代码,即获取代理对象
     * @return 代理对象
     */
    public static Object getProxyInstance() {
        //Enhancer是CGLIB的字节码增强器,可以很方便的对类进行拓展
        Enhancer enhancer = new Enhancer();
        //为代理类设置父类,即代理类继承被代理类
        enhancer.setSuperclass(InfoDemo.class);
        //为代理类设置回调对象,即自定义的方法拦截器
        enhancer.setCallback(new MyMethodInterceptor());
        //创建代理对象
        return enhancer.create();
    }

    public static void main(String[] args) {
        //将代理类class文件存入本地磁盘,方便反编译查看源码
        String path = "D:\\ProgramFiles\\workspace\\zznode\\data-query\\tx-study\\src\\main\\java\\com\\tx\\study\\others\\proxy\\cglibProxy\\";
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, path);
        //获取代理对象
        InfoDemo instance = (InfoDemo) getProxyInstance();
        //调用被代理方法(底层会调用自定义方法拦截器中重写的intercept方法)
        instance.welcome("zhangsan");
    }

}

运行结果:

before method!!!

welcome :zhangsan

after method!!!

2、cglib动态代理的原理

原理分析

执行测试类,会生成三个class文件,如下所示,通过反编译可以看到这三个文件中的源码。

InfoDemo$$EnhancerByCGLIB$$8b8da05b.class (cglib生成的代理类,继承InfoDemo类)

InfoDemo$$FastClassByCGLIB$$f4c7f3ac.class (被代理类的FastClass,记为f1)

InfoDemo$$EnhancerByCGLIB$$8b8da05b$$FastClassByCGLIB$$9065a6c.class (代理类的FastClass,记为f2)(在下面会详细说明这三个类的部分源码)

在MethodProxy类中有一个静态内部类FastClassInfo,如下所示。FastClassInfo中有四个属性,FastClass f1表示被代理类的FastClass,FastClass f2表示代理类的FastClass,int i1表示被代理类的方法签名(实际上是方法对应的索引,根据该索引可以快速找到对应的方法,这就是cglib动态代理比JDK动态代理效率高的主要原因),int i2表示代理类的方法签名。通过FastClassInfo对象可以得到被代理类和代理类的FastClass,在下面会详细介绍。

java 复制代码
private static class FastClassInfo {
    FastClass f1;//被代理类的FastClass
    FastClass f2;//代理类的FastClass
    int i1; //被代理类的方法签名(index)
    int i2;//代理类的方法签名

    private FastClassInfo() {
    }
}

先看一下本例中生成的代理类(InfoDemo$$EnhancerByCGLIB$$8b8da05b.class)的源码。

代理类 InfoDemo$$EnhancerByCGLIB$$8b8da05b.class

java 复制代码
public class InfoDemo$$EnhancerByCGLIB$$8b8da05b extends InfoDemo implements Factory {
    private MethodInterceptor CGLIB$CALLBACK_0;      //拦截器
    private static final Method CGLIB$welcome$0$Method;   //被代理方法(是Method对象)
    private static final MethodProxy CGLIB$welcome$0$Proxy; //方法代理(MethodProxy对象)
    //..........................................省略
    static void CGLIB$STATICHOOK1() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        Class var0 = Class.forName("CglibTest.InfoDemo$$EnhancerByCGLIB$$8b8da05b");
        Class var1;
		//通过反射获取被代理方法
        CGLIB$welcome$0$Method = ReflectUtils.findMethods(new String[]{"welcome", "(Ljava/lang/String;)V"}, (var1 = Class.forName("CglibTest.InfoDemo")).getDeclaredMethods())[0];
		//生成与被代理方法对应的方法代理对象
        CGLIB$welcome$0$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/String;)V", "welcome", "CGLIB$welcome$0");
        //..........................................省略
    }
	
	//cglib会生成与被代理方法对应的代理方法CGLIB$welcome$0
	final void CGLIB$welcome$0(String var1) {
		//直接调用父类的被代理方法(实际上被代理方法就是在此处被调用的)
        super.welcome(var1);
    }
	
    //由于代理类继承了被代理类,所以在代理类中生成了被代理方法(进行了重写)
    public final void welcome(String var1) {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if(this.CGLIB$CALLBACK_0 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }
		//判断代理类中是否设置了方法拦截,如果设置了就调用该拦截器的intercept方法
    	//在本例中设置了拦截器enhancer.setCallback(new MyMethodInterceptor());
        if(var10000 != null) {
            var10000.intercept(this, CGLIB$welcome$0$Method, new Object[]{var1}, CGLIB$welcome$0$Proxy);
        } else {
			//如果没有设置拦截器,就直接调用父类的被代理方法
            super.welcome(var1);
        }
    }
    //..........................................省略
}

在代理类中会生成与父类(即被代理类)中每一个方法相对应的两个方法,例如此例中父类的welcome方法,在代理类中生成了CGLIB$welcome$0代理方法和重写的welcome方法。另外,父类中的每一个方法都会在静态块中,通过MethodProxy.create生成对应的方法代理。

在本例测试类中,代理对象调用父类的被代理方法,即instance.welcome("zhangsan"),实际上调用的是代理类中被重写后的welcome方法,即public final void welcome(String var1)。在该方法中调用了拦截器MethodInterceptor的intercept方法。由于自定义的方法拦截器实现了MethodInterceptor接口,并对intercept方法进行了重写,所以,实际上调用的是重写后的intercept方法。在重写的intercept方法中调用了MethodProxy对象的invokeSuper(o, objects)方法,接下来重点看一下invokeSuper方法的执行过程,即分析MethodProxy的源码。

先看一下代理类中调用的intercept(this, CGLIB$welcome0Method, new Object[]{var1}, CGLIB$welcome0Proxy)方法,该方法中有四个参数,第一个参数是this,即调用代理类中重写后的welcome方法的对象,当然是代理对象了。第二个参数是被代理方法(实际上是Method对象),第三个参数是被代理方法的参数数组,第四个参数是方法代理对象(即MethodProxy对象)。这四个参数也就是自定义方法拦截器的intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)方法中的四个参数。所以,methodProxy.invokeSuper(o, objects)方法中的methodProxy就是方法代理对象,o就是代理对象,objects就是被代理方法的参数。

MethodProxy 源码分析

java 复制代码
public class MethodProxy {
    //下面的前三个变量在create方法中,都已经得到了初始值了。
    private Signature sig1;
    private Signature sig2;
    private MethodProxy.CreateInfo createInfo;
    //在调用invoke或者invokeSuper中,通过init()方法生成FastClassInfo
    private volatile MethodProxy.FastClassInfo fastClassInfo;

    public Object invokeSuper(Object obj, Object[] args) throws Throwable {
        try {
            this.init();
            MethodProxy.FastClassInfo fci = this.fastClassInfo;
			//调用代理类的FastClass的invoke方法
            return fci.f2.invoke(fci.i2, obj, args);
        } catch (InvocationTargetException var4) {
            throw var4.getTargetException();
        }
    }
    
	public Object invoke(Object obj, Object[] args) throws Throwable {
        try {
            this.init();
            MethodProxy.FastClassInfo fci = this.fastClassInfo;
			//调用被代理类的FastClass的invoke方法
            return fci.f1.invoke(fci.i1, obj, args);
        } catch (InvocationTargetException var4) {
            throw var4.getTargetException();
        } catch (IllegalArgumentException var5) {
            if(this.fastClassInfo.i1 < 0) {
                throw new IllegalArgumentException("Protected method: " + this.sig1);
            } else {
                throw var5;
            }
        }
    }
    
	//init方法就是为了生成FastClassInfo,FastClassInfo中存放着两个fastclass(f1、f2)和两个方法索引的值(i1、i2)。
    private void init() {
        if(this.fastClassInfo == null) {
            Object var1 = this.initLock;
            synchronized(this.initLock) {
                if(this.fastClassInfo == null) {
                    MethodProxy.CreateInfo ci = this.createInfo;
                    MethodProxy.FastClassInfo fci = new MethodProxy.FastClassInfo();
                    //不是每一个方法都生成一个fastclass,每一个方法的fastclass都是一样的,
                    //只不过他们的i1,i2不一样。如果缓存中有就取出,没有就生成新的FastClass
                    fci.f1 = helper(ci, ci.c1);
                    fci.f2 = helper(ci, ci.c2);
                    fci.i1 = fci.f1.getIndex(this.sig1);
                    fci.i2 = fci.f2.getIndex(this.sig2);
                    this.fastClassInfo = fci;
                    this.createInfo = null;
                }
            }
        }
    }

	//根据一个类的信息,返回该对象的一个Fastclass
    private static FastClass helper(MethodProxy.CreateInfo ci, Class type) {
        Generator g = new Generator();
        g.setType(type);
        g.setClassLoader(ci.c2.getClassLoader());
        g.setNamingPolicy(ci.namingPolicy);
        g.setStrategy(ci.strategy);
        g.setAttemptLoad(ci.attemptLoad);
        return g.create();
	}
	
	//FastClassInfo是MethodProxy的静态内部类
	private static class FastClassInfo {
    	FastClass f1;//被代理类的FastClass
    	FastClass f2;//代理类的FastClass
    	int i1; //被代理类的方法签名(index)
    	int i2;//代理类的方法签名
    	private FastClassInfo() {
    	}
	}
}

在MethodProxy类的invokeSuper方法中调用了代理类的FastClass的invoke方法,接下来看一下代理类的FastClass的源码。代理类的FastClass的字节码反编译后的源码如下所示。

代理类的 FastClass

代理类的FastClass(InfoDemo$$EnhancerByCGLIB$$8b8da05b$$FastClassByCGLIB$$9065a6c)的字节码反编译后的源码:

java 复制代码
public class InfoDemo$$EnhancerByCGLIB$$8b8da05b$$FastClassByCGLIB$$9065a6c extends FastClass {
    public InfoDemo$$EnhancerByCGLIB$$8b8da05b$$FastClassByCGLIB$$9065a6c(Class var1) {
        super(var1);
    }
    public int getIndex(Signature var1) {
        String var10000 = var1.toString();
        switch(var10000.hashCode()) {
        case -2055565910:
            if(var10000.equals("CGLIB$SET_THREAD_CALLBACKS([Lnet/sf/cglib/proxy/Callback;)V")) {
                return 12;
            }
            break;
        case -1725733088:
            if(var10000.equals("getClass()Ljava/lang/Class;")) {
                return 24;
            }
        case 1013143764:
            if(var10000.equals("CGLIB$welcome$0(Ljava/lang/String;)V")) {
                return 17;
            }
        }
        //----省略
    }
	
    //在MethodProxy类的invokeSuper方法中调用了此处的invoke方法
    public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
        8b8da05b var10000 = (8b8da05b)var2;
        int var10001 = var1;
        try {
            switch(var10001) {
            case 0:
                return var10000.toString();
            case 1:
                return new Integer(var10000.hashCode());
            case 17:
				//调用代理对象的代理方法CGLIB$welcome$0
                var10000.CGLIB$welcome$0((String)var3[0]);
            }
            //----省略
        }
    }
}

由上述代码可知,在代理类的FastClass的invoke方法中调用了代理对象的CGLIB$welcome0方法,由上面分析的代理类的源码可知,在代理类的CGLIBwelcome$0方法中直接调用父类的被代理方法,即调用此例中InfoDemo类的welcome方法,执行被代理方法。至此,cglib动态代理的大致过程就分析完毕了。

如果在自定义方法拦截器中调用的不是**methodProxy.invokeSuper(o, objects)**方法,而是methodProxy.invoke(o, objects)方法,为什么会引起死循环,导致内存溢出呢?由MethodProxy的源码可知,在MethodProxy的invoke方法中调用了被代理类的FastClass的invoke方法,所以,接下来看一下被代理类的FastClass(InfoDemo$$FastClassByCGLIB$$f4c7f3ac.class)被反编译后的部分源码。

java 复制代码
public class InfoDemo$$FastClassByCGLIB$$f4c7f3ac extends FastClass {
public Object invoke(Object obj, Object[] args) throws Throwable {
    try {
        switch(var10001) {
        case 0:
			//调用被代理对象的被代理方法welcome
            var10000.welcome((String)var3[0]);
            return null;
        case 1:
        }
    }
}
}

在被代理类的FastClass的invoke方法中调用被代理对象的welcome方法,通过代理对象调用被代理方法,与在main函数中instance.welcome("zhangsan")是一样的步骤,即又从头开始循环调用,直到栈内存溢出。

3、两种动态代理方式的对比

相关推荐
尘浮生22 分钟前
Java项目实战II基于Spring Boot的智慧生活商城系统的设计与实现(开发文档+数据库+源码)
java·开发语言·数据库·spring boot·mysql·maven·生活
疯一样的码农2 小时前
Java初学者指南
java·开发语言
LUwantAC3 小时前
Java学习路线:JUL日志系统(一)日志框架介绍
java·开发语言·学习
佚先森4 小时前
IAPP仿源码大师主界面UI
java
q567315234 小时前
使用 Python 编辑 XML 文件中的文本字段
xml·java·数据库·python·sqlite
LeMay084 小时前
基础算法——排序算法(冒泡排序,选择排序,堆排序,插入排序,希尔排序,归并排序,快速排序,计数排序,桶排序,基数排序,Java排序)
java·算法·排序算法
Mr。轩。4 小时前
cn.afterturn.easypoi.exception.excel.ExcelExportException: Excel导出错误 -> 修正过程。
java·excel·导出
大山很山5 小时前
Python简介和程序设计思想 |【python技能树知识点1~2】
java·网络·python
果粒陈爱写代码5 小时前
SpringBoot day 1105
java·spring boot·后端
colman wang5 小时前
Java入门二刷
java·开发语言