代码实现动态代理——基于jdk、基于cglib

一、jdk动态代理

1.1 概念和流程

jdk动态代理,是基于接口的代理。其工作原理可概括为在运行时动态生成实现指定接口的代理类,代理类会拦截所有接口方法的调用,将调用转发给一个 InvocationHandler InvocationHandler在invoke方法中进行逻辑的增强,或通过反射调用目标对象的真实方法。

工作流程如下:

客户端调用代理对象方法 → 代理类转发到 InvocationHandler → InvocationHandler执行 invoke 方法

1.1.2 Proxy.newProxyInstance() 生成代理类

动态生成代理类的过程如下,Proxy.newProxyInstance()是Java反射API中的一个静态方法,它允许在运行时创建一个实现类指定接口的代理类实例:

函数包含三个参数:

① loader, 定义代理类的类加载器;通常使用目标类对象的类加载器。

② interfaces,定义代理类所需实现的接口。

③ h,一个实现了 InvocationHandler 接口的对象,负责处理代理类方法的调用。

当调用Proxy.newProxyInstance() 时,JDK会

① 生成字节码:根据传入的接口 interfaces,动态生成一个实现接口的代理类($Proxy0, $Proxy1等);

② 编译加载:将生成的字节码编译为class对象,并通过指定的类加载器加载到JVM中。

③ 实例化代理类:创建代理类的实例,并将其与 h 绑定。

1.1.3 h, 实现InvocationHandler 接口

InvocationHandler是一个函数式接口,只有一个invoke函数,如下:

包含三个参数:

① proxy:代理对象本身(在函数体中慎用,避免递归调用);

② method:被调用的方法对象;

③ args:方法调用时的参数

当代理类会拦截到接口方法的调用时,会将该调用转发给 InvocationHandler,InvocationHandler就会自动执行 invoke 方法然后返回结果。

1.2 代码实例

定义一个StudentI 接口,包含 study 和 eat 两个方法:

public interface StudentI {
	public void study(String studentName);
	public void eat();
}

定义一个实现了 StudentI 的类 StudentImpl:

public class StudentImpl implements StudentI{
	@Override
	public void study(String studentName) {
		System.out.println(studentName + " is studying...");

	}

	@Override
	public void eat() {
		System.out.println("eating...");
	}
}

接下来,为StudentI生成代理类 StudentProxy,在 invoke 中插入增强代码:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class StudentProxy implements InvocationHandler {

	private Object target;  // 代理目标,要代理的对象

	// 生成代理对象的方法
	public Object bind(Object target) {
		this.target = target;
		return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
		// classLoader, interface, invocationHandler: 动态代理方法在执行时,会调用h里的invoke方法去执行
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		/**
		 * proxy 为代理对象,就是bind方法生成的对象
		 * method 为当前调度的方法
		 * args为调度方法的参数
		 */
		if (method.getName().equals("study")) { // 增强study方法
			System.out.println("proxy the study method, jdk, study before");
			Object object = method.invoke(target, args);
			System.out.println("jdk,, study after");
			return object;
		}
		if (method.getName().equals("eat")){  // 增强eat方法
			System.out.println("proxy the eat method, jdk, eat before");
			Object object = method.invoke(target, args);
			System.out.println("jdk,, eat after");
			return object;
		}
		return method.invoke(target, args);
	}
}

Main函数中测试一下:

public class TestMain {
	public static void main(String[] args) {
		StudentProxy stuProxy = new StudentProxy();
		StudentI stuImplProxy =(StudentI) stuProxy.bind(new StudentImpl());
		stuImplProxy.study("kiki");
		stuImplProxy.eat();
	}
}

运行结果如下:

1.3 jdk动态代理的应用

① SpringAOP默认使用的是 jdk 动态代理。SpringAOP 通过  jdk 动态代理实现了运行时、基于接口的方法拦截,结合 AspectJ 注解(如 @Before@Around),可优雅地实现事务管理、日志记录、权限校验等横切关注点。

②  Mybatis 的 Mapper 接口的工作原理也是  jdk动态代理。Mybatis运行时会使用  jdk 动态代理为Mapper接口生成代理对象 proxy, 代理对象会拦截接口方法,转而执行 mapperStatement 所代表的sql, 然后将 sql 执行结果返回。

二、cglib动态代理

2.1 概念和流程

cglib 动态代理,是基于继承的代理。它通过字节码技术在运行时为类创建代理子类,实现方法拦截和增强。

cglib 工作流程如下:

① 继承目标类。通过ASM(Java字节码操作框架,生成字节码、编译加载类、实例化)动态生成目标类的子类 ($$EnhancerByCGLIB)。

② 重写方法。在子类中重写目标类的方法,插入拦截逻辑。

③ MethodInterceptor 拦截。代理对象的方法调用会被转发到 MethodInterceptor,由其决定是否执行目标方法或增强逻辑。

2.1.1 Enhancer 生成代理对象,配置拦截器

Enhancer 是 cglib 的核心类,用于创建代理对象:

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(TargetClass.class); // 设置父类(目标类)
enhancer.setCallback(new MyMethodInterceptor()); // 设置拦截器
TargetClass proxy = (TargetClass) enhancer.create(); // 创建代理对象

当调用 enhancer.create()时,cglib会:

① 分析目标类的所有方法,

② 为每个可代理的方法(非private、非final)生成对应的methodProxy实例

③ 将这些实例存储在代理类(对应上面的proxy)中,作为静态字段或方法局部变量。

2.1.2 拦截器 MethodInterceptor

MethodInterceptor 是一个函数时接口,仅包含 intercept 函数:

四个参数:

obj:代理对象本身。

method: 被拦截的方法。

args:方法参数。

proxy:代理对象,可以用于高效调用父类方法(proxy.invokeSuper),比反射更快。

2.2 代码实例

生成一个PersonService类,有一个 sayHello() 方法,是需要被代理的类。

public class PersonService {
	public void sayHello(String name) {
		System.out.println("hello: " + name);
	}
}

在TestCase的main函数中实现代理:

public class TestCase {
	public static void main(String[] args) {

		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(PersonService.class); // 设置父类,即代理目标,代理对象
		enhancer.setCallback((MethodInterceptor) (obj,method,in_args,proxy)->{ // 设置拦截器MethodInterceptor,这里是lambda表达式实现函数式接口
			System.out.println("before");
			Object result = proxy.invokeSuper(obj,in_args); // 调用父类,即目标类的方法
			// cglib 性能更高效的原因在此,通过MethodProxy.invokeSuper调用目标类的方法,内部通过字节码生成直接调用父类的方法,避免反射
			// jdk 通过 method.invoke 反射通过方法名和参数类型动态查找并调用方法,设计类加载、方法解析等,性能开销更大!
			System.out.println("after");
			return result;
		});

		PersonService personServiceProxy = (PersonService) enhancer.create();
		personServiceProxy.sayHello("xixi");
	}
}

PersonService personServiceProxy = (PersonService) enhancer.create();
personServiceProxy.sayHello("xixi");

上述两句代码执行后,解析拦截器 MethodInterceptor 对应的实参:

obj:代理类的实例本身,也就是 personServiceProxy;

method:PersonService.sayHello 方法对象;

in_args: 函数参数,“xixi”;

proxy:cglib通过字节码技术已生成sayHello方法对应的methodProxy实例,直接通过 proxy.invokeSuper即可调用。

// cglib 性能更高效的原因在此,通过MethodProxy.invokeSuper调用目标类的方法,内部通过字节码生成直接调用父类的方法,避免反射
// jdk 通过 method.invoke 反射通过方法名和参数类型动态查找并调用方法,设计类加载、方法解析等,性能开销更大!

2.3 cglib动态代理的应用

① 当目标对象实现接口时,SpringAOP 默认用jdk动态代理;如果目标对象未实现接口,SpringAOP 使用 cglib 动态代理。

② SpringBoot2 AOP 默认使用 cglib 动态代理。

SpringBoot 2.x 为何默认使用 Cglib 参考SpringBoot 2.x 为何默认使用 Cglib

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值