IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Java动态代理CGLIB详解 -> 正文阅读

[Java知识库]Java动态代理CGLIB详解

5362b8e6a05e4ff68cd2568f1e6c4542.pngCGLIB介绍

?

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知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-06-03 23:53:36  更:2022-06-03 23:54:35 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 19:50:11-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码