Spring源码系列(五):JDK、CGLIB动态代理源码解析

297 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第4天,点击查看活动详情

JDK动态代理和Cglib动态代理的区别

  1. JDK动态代理是Java自带的,CGLIB动态代理是第三方jar包提供的。
  2. JDK动态代理是针对有接口的目标类进行动态代理,CGLIB动态代理是非final类都可以进行动态代理。但是Spring优先使用JDK动态代理。
  3. JDK动态代理实现的逻辑是目标类和代理类都实现同一个接口,目标类和代理类是平级的。而CGLIB动态代理实现逻辑是给目标类生个孩子(子类,也就是代理类),目标类和代理类是父子继承关系。
  4. JDK动态代理在早期的JDK1.6左右性能比CGLIB动态代理差,但在JDK1.8以后两种代理方式性能基本差不多,反正JDK动态代理更加优越。

动态代理主要关注两个点:

  • 代理对象如何创建的
  • 代理对象如何执行的

JDK动态代理

产生代理对象的四个步骤

  1. JDK在运行时根据代理类的规则以及目标类相关信息,编写代理类的源代码(.java)
  2. JDK对java源代码进行编译(.class)
  3. JDK根据目标类的类加载器,去将代理类的class文件加载到JVM中
  4. JVM会根据加载到的class信息,产生一个代理对象

产生代理对象

JDK产生代理对象需要使Proxy.newProxyInstance方法。


// Proxy是JDK中的API类
// 第一个参数:目标对象的类加载器
// 第二个参数:目标对象的接口
// 第三个参数:代理对象的执行处理器
Object $Proxy11 = Proxy.newProxyInstance(
                            clazz.getClassLoader(), 
                            new Class[] { clazz },
                            new MyInvocationHandler(target));

为了加强理解,我们来手写一个代理类。

// 代理类$Proxy11 
public class $Proxy11 implements 目标类的接口集合{

   // 执行代理对象方法的类
   private InvocationHandler h;

   // 原对象的两个方法
   private static Method m1;
   private static Method m2;

   // 构造函数
   public $Proxy11(InvocationHandler h) {
       this.h = h;
   }

   // 增强后的原目标对象中的方法
   public void save() {
       try {
           this.h.invoke(this, m1, null);
       } catch (Throwable throwable) {
           throwable.printStackTrace();
       }
   }

   public void update() {
       try {
           this.h.invoke(this, m2, null);
       } catch (Throwable throwable) {
           throwable.printStackTrace();
       }
   }

   // 静态代码块,在类加载时执行
   static {
       try {
           // 得到目标类两个方法
           m1 = Class.forName("接口类的全路径").getDeclaredMethod("目标类中的方法名称");
           m2 = Class.forName("接口类的全路径").getDeclaredMethod("目标类中的方法名称");
       }catch (Exception e){
           e.printStackTrace();
       }
   }
}

执行代理对象方法

创建InvocationHandler接口的实现类,在invoke方法中实现代理逻辑。

当我们为目标类生成一个代理类后,在JVM中就会把目标类删除,只留下代理类。所以执行方法时,实际上是执行代理对象中的方法,而代理对象又会调用InvocationHandler中的方法。来完成对方法的增强。

public class MyInvocationHandler implements InvocationHandler {

	// 目标对象的引用
	private Object target;

	// 通过构造方法将目标对象注入到代理对象中
	public MyInvocationHandler(Object target) {
		super();
		this.target = target;
	}

	/**
	 * 代理对象会执行的方法
	 * 第一个参数:代理对象本身
	 * 第二个参数:目标对象的方法对象(Method对象),一个方法针对一个Method对象
	 * 第三个参数:目标对象的方法参数
	 * 
	 * 代理对象执行的逻辑:
	 * 		需要执行目标对象的原方法?
	 * 			如何执行目标对象的原方法呢?
	 * 			该处使用的是反射
	 * 			【要调用方法的Method对象.invoke(要调用方法的对象,要调用方法的参数)】
	 * 		只是在调用目标对象的原方法前边和后边可能要加上一些增强功能的代码
	 * 
	 * 增强代码比如:
	 * 		在原方法调用之前,开启事务,源方法结束之后,提交和回滚事务
	 * 
	 */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

		System.out.println("这是jdk的代理方法");
		// 下面的代码,是反射中的API用法
		// 该行代码,实际调用的是[目标对象]的方法
		// 利用反射,调用[目标对象]的方法
		Object returnValue = method.invoke(target, args);
		// AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
		
		// ReflectiveMethodInvocation.proceed()进行递归增强
		
		// 增强的部分
		return returnValue;
	}
}

CGLIB动态代理

产生代理对象

    // 创建增强器
    Enhancer enhancer = new Enhancer();
    // 设置需要增强的类的类对象
    enhancer.setSuperclass(clazz);
    // 设置回调函数
    enhancer.setCallback(new MyMethodInterceptor());
    // 获取增强之后的代理对象
    Object object = enhancer.create();

执行代理对象方法

public class MyMethodInterceptor implements MethodInterceptor {

	/***
	 * Object proxy:这是代理对象,也就是[目标对象]的子类
	 * Method method:[目标对象]的方法 
	 * Object[] arg:参数
	 * MethodProxy methodProxy:代理对象的方法
	 */
	@Override
	public Object intercept(Object proxy, Method method, Object[] arg, MethodProxy methodProxy) throws Throwable {
		// 因为代理对象是目标对象的子类
		// 该行代码,实际调用的是父类目标对象的方法
		System.out.println("这是cglib的代理方法");

		// 通过调用子类[代理类]的invokeSuper方法,去实际调用[目标对象]的方法
		// Object returnValue = method.invoke(target, arg);
		// 错误示例(死循环):Object returnValue = method.invoke(proxy, arg);
		Object returnValue = methodProxy.invokeSuper(proxy, arg);
		
		// method.invoke(target, arg);
		
		//目标对象的saveUser(){}
		//代理对象saveUser(){
		//super();
		//}
		
		// 代理对象调用代理对象的invokeSuper方法,而invokeSuper方法会去调用目标类的invoke方法完成目标对象的调用

		return returnValue;
	}

}