1. 程式人生 > >27--靜態代理模式和JDK、CGLIB動態代理

27--靜態代理模式和JDK、CGLIB動態代理

前面的章節,已經分析了IoC容器的原始碼,接下來的章節來分析Spring的另一個核心功能AOP。為了更好的分析原始碼,需要先溫習一下動態代理的知識,如果對java的動態代理無所瞭解的話,那麼對AOP原始碼的分析就無從談起。代理模式可分為靜態代理和動態代理兩種。而動態代理又有JDK、CGLIB動態代理。下面我們逐步分析這幾種代理。

1.靜態代理

  • 被代理介面和實現類
package com.lyc.cn.v2.day04.proxy.proxy;

/**
 * 賬戶介面
 */
public interface Count {
	// 查詢賬戶
	void queryCount();

	// 修改賬戶
	void
updateCount(); }
package com.lyc.cn.v2.day04.proxy.proxy;

/**
 * @author: LiYanChao
 * @create: 2018-10-21 12:07
 */
public class CountImpl implements Count {
	@Override
	public void queryCount() {
		System.out.println("==查詢賬戶");
	}

	@Override
	public void updateCount() {
		System.out.println("==更新賬戶"
); } }

介面只模擬了兩個介面,賬戶查詢和賬戶更新,並在實現類裡進行了列印。

  • 代理類
package com.lyc.cn.v2.day04.proxy.proxy;

/**
 * 代理類
 * @author: LiYanChao
 * @create: 2018-10-21 12:08
 */
public class CountProxy implements Count {

	private CountImpl countImpl;

	/**
	 * 覆蓋預設構造器
	 */
	public CountProxy(CountImpl countImpl) {
		this.countImpl =
countImpl; } @Override public void queryCount() { System.out.println("==查詢賬戶開始"); // 呼叫真正的查詢賬戶方法 countImpl.queryCount(); System.out.println("==查詢賬戶結束"); } @Override public void updateCount() { System.out.println("==更新賬戶開始"); // 呼叫真正的修改賬戶操作 countImpl.updateCount(); System.out.println("==更新賬戶結束"); } }
  • 測試及結果
@Test
public void test1() {
    // 靜態代理
    CountImpl countImpl = new CountImpl();
    CountProxy countProxy = new CountProxy(countImpl);
    countProxy.updateCount();
    System.out.println("\n*******\n");
    countProxy.queryCount();
}
==更新賬戶開始
==更新賬戶
==更新賬戶結束

*******

==查詢賬戶開始
==查詢賬戶
==查詢賬戶結束

該中模式比較簡單,不再做過多的分析。

2.JDK動態代理

JDK動態代理所用到的代理類在程式呼叫到代理類物件時才由JVM真正建立,JVM根據傳進來的業務實現類物件以及方法名 ,動態地建立了一個代理類的class檔案並被位元組碼引擎執行,然後通過該代理類物件進行方法呼叫。我們需要做的,只需指定代理類的預處理、呼叫後操作即可。JDK的動態代理需要實現InvocationHandler介面,並重寫invoke方法。並且被代理的類必須有介面,來看具體的例子:

  • 被代理介面和類
package com.lyc.cn.v2.day04.proxy.jdk;

public interface JDKAnimal {
	void sayHello();
}
package com.lyc.cn.v2.day04.proxy.jdk;

public class JDKDog implements JDKAnimal {
	@Override
	public void sayHello() {
		System.out.println("我是一隻貓");
	}
}
  • 自定義InvocationHandler
package com.lyc.cn.v2.day04.proxy.jdk;

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

public class MyInvocationHandler implements InvocationHandler {

	// 目標物件
	private Object target;

	/**
	 * 構造方法
	 * @param target 目標物件
	 */
	public MyInvocationHandler(Object target) {
		super();
		this.target = target;
	}

	/**
	 * @param proxy  JDK動態生成的最終代理物件
	 * @param method 呼叫真實物件的某個方法的Method物件
	 * @param args   呼叫真實物件某個方法時接受的引數
	 */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("==代理方法開始執行");
		Object invoke = method.invoke(target, args);
		System.out.println("==代理方法結束執行");
		return invoke;
	}

	/**
	 * 獲取目標物件的代理物件
	 * @return 代理物件
	 */
	public Object getProxy() {
		/**
		 * 引數:
		 * 1、獲取當前執行緒的類載入
		 * 2、獲取介面
		 * 3、當前物件
		 */
		return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
				target.getClass().getInterfaces(),
				this);
	}

}
  • 測試及結果
@Test
public void test2() {
    // JDK動態代理
    MyInvocationHandler handler = new MyInvocationHandler(new JDKDog());
    JDKAnimal proxy = (JDKAnimal) handler.getProxy();
    proxy.sayHello();
}
==代理方法開始執行
我是一隻貓
==代理方法結束執行
3.JDK動態代理原理分析

在MyInvocationHandler類中,通過實現InvocationHandler並重寫invoke方法,實現了對JDKDog類的動態代理。但是這裡大家一定會有一個疑問,在測試類中通過JDKAnimal proxy = (JDKAnimal) handler.getProxy();獲取了代理類的例項,但是當呼叫proxy.sayHello();方法時,卻會呼叫MyInvocationHandler的invoke方法,要解開這個謎題,就需要了解一下代理類是如何生成、生成的代理類是什麼樣子的、是如何被例項化的。

  • Proxy.newProxyInstance方法簡析 開啟MyInvocationHandler類的getProxy方法,檢視Proxy.newProxyInstance方法原始碼:
public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
{
    Objects.requireNonNull(h);

    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }

    /*
     * Look up or generate the designated proxy class.
     */
    Class<?> cl = getProxyClass0(loader, intfs);

    /*
     * Invoke its constructor with the designated invocation handler.
     */
    try {
        if (sm != null) {
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }

        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        if (!Modifier.isPublic(cl.getModifiers())) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
        }
        // 建立生成代理類的例項
        return cons.newInstance(new Object[]{h});
    } catch (IllegalAccessException|InstantiationException e) {
        throw new InternalError(e.toString(), e);
    } catch (InvocationTargetException e) {
        Throwable t = e.getCause();
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            throw new InternalError(t.toString(), t);
        }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString(), e);
    }
}

如果大家對這段程式碼不感興趣或者覺得很枯燥的話,沒關係,大家只要知道在這個方法裡生成了代理類的例項就行了,但是生成的代理類是什麼樣子的呢?可以通過ProxyGenerator來幫助我們。

  • 生成代理類的.class檔案
package com.lyc.cn.v2.day04.proxy.jdk;

import org.junit.Test;
import sun.misc.ProxyGenerator;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * @author: LiYanChao
 * @create: 2018-11-02 14:11
 */
public class GenJdkProxyClass {

	/**
	 * 生成代理類的名稱
	 */
	private static String DEFAULT_CLASS_NAME = "$Proxy";

	/**
	 * 預設生成的檔案全路徑
	 */
	private static String DEFAULT_FILE_PATH = "/Users/liyanchao/Desktop/" + DEFAULT_CLASS_NAME + ".class";


	/**
	 * 使用ProxyGenerator生成代理類.class檔案
	 * @param path 檔案路徑
	 */
	public static void genProxyClass(String path) {

		byte[] classFile = ProxyGenerator.generateProxyClass(DEFAULT_CLASS_NAME, new Class[]{JDKAnimal.class});
		FileOutputStream fos = null;
		try {
			fos = new FileOutputStream(path);
			fos.write(classFile);
			fos.flush();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (fos != null) {
				try {
					fos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	@Test
	public void TestGenProxyClass() {
		GenJdkProxyClass.genProxyClass(DEFAULT_FILE_PATH);
	}
}

可以根據自己的需要修改生成的類名和路徑。執行測試方法生成.class文並進行反編譯,如果沒有反編譯軟體的話,可以下載JD-GUI 可以實現對.class檔案的反編譯,該軟體支援windows、linux、macOS。用JD-GUI開啟生成的$Proxy.class檔案,並將反編譯的類匯入IDEA(直接在IDEA裡新建同名檔案將程式碼複製貼上進去即可)。反編譯後的原始碼如下:

package com.lyc.cn.v2.day04.proxy.jdk;

/**
 * 反編譯代理.class檔案
 * @author: LiYanChao
 * @create: 2018-11-02 15:01
 */

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

public final class $Proxy extends Proxy implements JDKAnimal {
	private static Method m1;
	private static Method m3;
	private static Method m2;
	private static Method m0;

	public $Proxy(InvocationHandler paramInvocationHandler) {
		super(paramInvocationHandler);
	}

	public final boolean equals(Object paramObject) {
		try {
			return ((Boolean) this.h.invoke(this, m1, new Object[]{paramObject})).booleanValue();
		} catch (Error | RuntimeException localError) {
			throw localError;
		} catch (Throwable localThrowable) {
			throw new UndeclaredThrowableException(localThrowable);
		}
	}

	/**
	 * 代理類中實現了sayHello介面
	 */
	public final void sayHello() {
		try {
			this.h.invoke(this, m3, null);
			return;
		} catch (Error | RuntimeException localError) {
			throw localError;
		} catch (Throwable localThrowable) {
			throw new UndeclaredThrowableException(localThrowable);
		}
	}

	public final String toString() {
		try {
			return (String) this.h.invoke(this, m2, null);
		} catch (Error | RuntimeException localError) {
			throw localError;
		} catch (Throwable localThrowable) {
			throw new UndeclaredThrowableException(localThrowable);
		}
	}

	public final int hashCode() {
		try {
			return ((Integer) this.h.invoke(this, m0, null)).intValue();
		} catch (Error | RuntimeException localError) {
			throw localError;
		} catch (Throwable localThrowable) {
			throw new UndeclaredThrowableException(localThrowable);
		}
	}

	static {
		try {
			m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
			m3 = Class.forName("com.lyc.cn.v2.day04.proxy.jdk.JDKAnimal").getMethod("sayHello", new Class[0]);
			m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
			m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
			// return 會報錯,註釋掉即可。
			//return;
		} catch (NoSuchMethodException localNoSuchMethodException) {
			throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
		} catch (ClassNotFoundException localClassNotFoundException) {
			throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
		}
	}
}

代理類$Proxy被宣告為final類,繼承Proxy類並實現了JDKAnimal介面,重寫了equals、toString、hashCode等方法,當然最重要的還是實現了JDKAnimal介面中的sayHello方法,並通過靜態程式碼塊拿到了sayHello方法的資訊,看到sayHello方法體,看到這裡相信大家對前面提到的疑問已經恍然大悟了。再通過一個測試方法加深大家的印象:

@Test
public void test3() {
    // JDK動態代理測試反編譯後的生成代理類
    MyInvocationHandler handler = new MyInvocationHandler(new JDKDog());
    $Proxy $proxy = new $Proxy(handler);
    $proxy.sayHello();
}

new $Proxy(handler)建構函式接受的就是我們自定義的MyInvocationHandler,所以當代碼執行到$Proxy的sayHello方法時,this.h.invoke(this, m3, null); this.h就是MyInvocationHandler的例項,所以自然就會呼叫到invoke方法了,因為JDKAnimal proxy = (JDKAnimal) handler.getProxy();獲取到的是代理類的例項,而不是JDKAnimal的例項。

4.CGLIB動態代理

CGLIB是針對類來實現代理的,原理是對指定的業務類生成一個子類,並覆蓋其中業務方法實現代理。因為採用的是繼承,所以不能對final修飾的類進行代理。 在使用的時候需要引入cglib和asm的jar包,並實現MethodInterceptor介面。

本例是在Spring原始碼下新建的Gradle模組,所以引入jar包的方式如下,如果是maven工程或者其他的工程的話,可自行匯入jar包。

compile group: 'asm', name: 'asm', version: '3.3.1'
compile group: 'cglib', name: 'cglib', version: '2.2.2'
  • 被代理類
package com.lyc.cn.v2.day04.proxy.cglib;

public class CglibCat {
	public void sayHello() {
		System.out.println("我是一隻貓。。。");
	}
}
  • 自定義MethodInterceptor
package com.lyc.cn.v2.day04.proxy.cglib;

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

import java.lang.reflect.Method;

public class MyCglibProxy implements MethodInterceptor {

	private Enhancer enhancer = new Enhancer();

	// 這裡的目標型別為Object,則可以接受任意一種引數作為被代理類,實現了動態代理
	public Object getInstance(Class clazz) {
		enhancer.setSuperclass(clazz);
		enhancer.setCallback(this);
		// 返回代理物件
		return enhancer.create();
	}

	@Override
	public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
		System.out.println("==代理方法開始執行")