Java动态代理之一CGLIB详解

在上篇文章《Java代理模式及动态代理详解》中我们介绍了Java中的静态代理模式与动态代理模式,并以JDK原生动态代理作为示例进行讲解。本篇文章我们来介绍一下基于CGLIB实现的动态代理,并与原生动态代理进行对比。

CGLIB介绍

CGLIB(Code Generation Library)是一个开源、高性能、高质量的Code生成类库(代码生成包)。

它可以在运行期扩展Java类与实现Java接口。Hibernate用它实现PO(Persistent Object 持久化对象)字节码的动态生成,Spring AOP用它提供方法的interception(拦截)。

CGLIB的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。但不鼓励大家直接使用ASM框架,因为对底层技术要求比较高。

使用实例

首先,引入CGLIB的依赖:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.5</version>
</dependency>

这里我们以操作用户数据的UserDao为例,通过动态代理来对其功能进行增强(执行前后添加日志)。UserDao定义如下:

public class UserDao {

	public void findAllUsers(){
		System.out.println("UserDao 查询所有用户");
	}

	public String findUsernameById(int id){
		System.out.println("UserDao 根据ID查询用户");
		return "公众号:程序新视界";
	}
}

创建一个拦截器,实现接口net.sf.cglib.proxy.MethodInterceptor,用于方法的拦截回调。

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @author sec
 * @version 1.0
 * @date 2020/3/24 8:14 上午
 **/
public class LogInterceptor implements MethodInterceptor {

	/**
	 *
	 * @param obj 表示要进行增强的对象
	 * @param method 表示拦截的方法
	 * @param objects 数组表示参数列表,基本数据类型需要传入其包装类型,如int-->Integer、long-Long、double-->Double
	 * @param methodProxy 表示对方法的代理,invokeSuper方法表示对被代理对象方法的调用
	 * @return 执行结果
	 * @throws Throwable 异常
	 */
	@Override
	public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
		before(method.getName());
		// 注意这里是调用invokeSuper而不是invoke,否则死循环;
		// methodProxy.invokeSuper执行的是原始类的方法;
		// method.invoke执行的是子类的方法;
		Object result = methodProxy.invokeSuper(obj, objects);
		after(method.getName());
		return result;
	}

	/**
	 * 调用invoke方法之前执行
	 */
	private void before(String methodName) {
		System.out.println("调用方法" + methodName +"之【前】的日志处理");
	}

	/**
	 * 调用invoke方法之后执行
	 */
	private void after(String methodName) {
		System.out.println("调用方法" + methodName +"之【后】的日志处理");
	}
}

实现MethodInterceptor接口的intercept方法。该方法中参数:

  • obj:表示要进行增强的对象;
  • method:表示要被拦截的方法;
  • objects:表示要被拦截方法的参数;
  • methodProxy:表示要触发父类的方法对象。

在方法的内部主要调用的methodProxy.invokeSuper,执行的原始类的方法。如果调用invoke方法否会出现死循环。

客户端使用示例如下:

import net.sf.cglib.proxy.Enhancer;

public class CglibTest {

	public static void main(String[] args) {

		// 通过CGLIB动态代理获取代理对象的过程
		// 创建Enhancer对象,类似于JDK动态代理的Proxy类
		Enhancer enhancer = new Enhancer();
		// 设置目标类的字节码文件
		enhancer.setSuperclass(UserDao.class);
		// 设置回调函数
		enhancer.setCallback(new LogInterceptor());
		// create方法正式创建代理类
		UserDao userDao = (UserDao) enhancer.create();
		// 调用代理类的具体业务方法
		userDao.findAllUsers();
		userDao.findUsernameById(1);
	}
}

执行客户端的main方法打印结果如下:

调用方法findAllUsers之【前】的日志处理
UserDao 查询所有用户
调用方法findAllUsers之【后】的日志处理
调用方法findUsernameById之【前】的日志处理
UserDao 根据ID查询用户
调用方法findUsernameById之【后】的日志处理

可以看到,我们方法前后已经被添加上对应的“增强处理”。

反编译class

在main方法内的第一行我们也可以添加如下设置,来存储代理类的class文件。

// 代理类class文件存入本地磁盘,可反编译查看源码
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/zzs/temp");

再次执行程序,我们可以看到在对应目录下生成三个class文件:

UserDao$$EnhancerByCGLIB$$1169c462.class
UserDao$$EnhancerByCGLIB$$1169c462$$FastClassByCGLIB$$22cae79c.class
UserDao$$FastClassByCGLIB$$197ae7fa.class

部分反编译代码如下:

import java.lang.reflect.Method;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.core.Signature;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class UserDao$$EnhancerByCGLIB$$1169c462 extends UserDao 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 Object CGLIB$CALLBACK_FILTER;
    private static final Method CGLIB$findAllUsers$0$Method;
    private static final MethodProxy CGLIB$findAllUsers$0$Proxy;
    private static final Object[] CGLIB$emptyArgs;
    private static final Method CGLIB$findUsernameById$1$Method;
    private static final MethodProxy CGLIB$findUsernameById$1$Proxy;
    private static final Method CGLIB$equals$2$Method;
    private static final MethodProxy CGLIB$equals$2$Proxy;
    private static final Method CGLIB$toString$3$Method;
    private static final MethodProxy CGLIB$toString$3$Proxy;
    private static final Method CGLIB$hashCode$4$Method;
    private static final MethodProxy CGLIB$hashCode$4$Proxy;
    private static final Method CGLIB$clone$5$Method;
    private static final MethodProxy CGLIB$clone$5$Proxy;
    
    public final void findAllUsers() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            var10000.intercept(this, CGLIB$findAllUsers$0$Method, CGLIB$emptyArgs, CGLIB$findAllUsers$0$Proxy);
        } else {
            super.findAllUsers();
        }
    }

    final String CGLIB$findUsernameById$1(int var1) {
        return super.findUsernameById(var1);
    }

    public final String findUsernameById(int var1) {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        return var10000 != null ? (String)var10000.intercept(this, CGLIB$findUsernameById$1$Method, new Object[]{new Integer(var1)}, CGLIB$findUsernameById$1$Proxy) : super.findUsernameById(var1);
    }
    
    // ...
}

从反编译的源码可以看出,代理对象继承UserDao,拦截器调用intercept()方法,intercept()方法由自定义的LogInterceptor实现,所以最后调用LogInterceptor中的intercept()方法,从而完成了由代理对象访问到目标对象的动态代理实现。

CGLIB创建动态代理类过程

(1)查找目标类上的所有非final的public类型的方法定义;

(2)将符合条件的方法定义转换成字节码;

(3)将组成的字节码转换成相应的代理的class对象;

(4)实现MethodInterceptor接口,用来处理对代理类上所有方法的请求。

JDK动态代理与CGLIB对比

JDK动态代理:基于Java反射机制实现,必须要实现了接口的业务类才生成代理对象。

CGLIB动态代理:基于ASM机制实现,通过生成业务类的子类作为代理类。

JDK Proxy的优势:

最小化依赖关系、代码实现简单、简化开发和维护、JDK原生支持,比CGLIB更加可靠,随JDK版本平滑升级。而字节码类库通常需要进行更新以保证在新版Java上能够使用。

基于CGLIB的优势:

无需实现接口,达到代理类无侵入,只操作关心的类,而不必为其他相关类增加工作量。高性能。

小结

关于动态代理相关的实现就讲这么多,在具体的业务场景中如何选择可根据它们的优缺点进行针对性的比对。不过作为Java领域的标杆项目Spring,很多功能都选择使用CGLIB来实现。


---
程序新视界:精彩和成长都不容错过
![程序新视界-微信公众号](https://img2020.cnblogs.com/other/1742867/202003/1742867-20200324091908506-1175698480.png)

posted on 2020-03-24 09:19  程序新视界  阅读(1606)  评论(0编辑  收藏  举报

导航