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方法,在代理类中生成了CGLIBwelcome0代理方法和重写的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, CGLIBwelcome0Method, new Object\[\]{var1}, CGLIBwelcome0Proxy)方法,该方法中有四个参数,第一个参数是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方法中调用了代理对象的CGLIBwelcome0方法,由上面分析的代理类的源码可知,在代理类的CGLIBwelcome0方法中直接调用父类的被代理方法,即调用此例中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、两种动态代理方式的对比

相关推荐
卡尔特斯1 小时前
Android Kotlin 项目代理配置【详细步骤(可选)】
android·java·kotlin
白鲸开源1 小时前
Ubuntu 22 下 DolphinScheduler 3.x 伪集群部署实录
java·ubuntu·开源
ytadpole2 小时前
Java 25 新特性 更简洁、更高效、更现代
java·后端
纪莫2 小时前
A公司一面:类加载的过程是怎么样的? 双亲委派的优点和缺点? 产生fullGC的情况有哪些? spring的动态代理有哪些?区别是什么? 如何排查CPU使用率过高?
java·java面试⑧股
JavaGuide3 小时前
JDK 25(长期支持版) 发布,新特性解读!
java·后端
用户3721574261353 小时前
Java 轻松批量替换 Word 文档文字内容
java
白鲸开源3 小时前
教你数分钟内创建并运行一个 DolphinScheduler Workflow!
java
晨米酱3 小时前
JavaScript 中"对象即函数"设计模式
前端·设计模式
Java中文社群3 小时前
有点意思!Java8后最有用新特性排行榜!
java·后端·面试
代码匠心4 小时前
从零开始学Flink:数据源
java·大数据·后端·flink