Java 動態代理

 一、代理的概念與作用

 

        首先代理是一種常用的設計模式,其目的就是爲其它對象提供一個代理以控制對某個對象的訪問。代理類負責爲委託類預處理消息,過濾消息轉發消息,以及進行消息被委託執行後的後續處理。程序中的代理是:要爲已存在的多個具有相同接口的目標類的各個方法增加一些系統功能,編寫一個與目標類具有相同接口的代理類,代理類的每個方法調用目標類的相同方法,並在調用方法時加上系統功能的菜嗎。(類似於裝飾模式)

 

            代理模式類關係圖:

 

                                          

 

二、AOP(面向切面編程)

 

       面向切面編程是目前軟件開發中的一個熱點,也是 Spring 框架中的一個重要內容。利用 AOP 可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發效率。

 

          主要功能爲:日誌記錄、性能統計、安全控制、事務處理、異常處理等。

 

三、Java 動態代理技術

 

       如果爲系統中的各種接口的類增加代理功能,那就需要太多的代理類,全部採用靜態代理將是一件很恐怖的事情!JVM 可以在運行期間動態生成出類的字節碼,這種動態生成的類往往被使用作代理類,即動態代理。

 

          1、動態代理的注意要點:

 

         (1)JVM 生成的動態類必須實現一個或多個接口,所以 JVM 生成的動態類只能用作具有相同接口的目標代理類的代理。

         (2)如果要代理的類並沒有實現任何接口,那麼 CGLIB 庫可以動態生成一個類的子類,一個類的子類也可以用作該類的代理。

         (3)代理類的各個方法中通常除了要調用目標的相應方法和對外返回目標返回的結果外,還可以在代理方法中的四個位置加上系統功能代碼:1、在調用目標方法之前。2、在調用目標方法之後。3、在調用目標方法的前和後。4、在處理目標方法異常的 catch 塊中。

 

          編程實例:創建 Collection 的代理類,並且返回該代理類的構造方法和普通方法。

public class A {

	public static void main(String[] args) throws Exception {

		Class clazzProxy = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class); // 獲取動態代理類的字節碼
		Constructor[] constructors = clazzProxy.getConstructors(); // 通過字節碼獲取代理類的所有構造方法
		Method[] methods = clazzProxy.getMethods();	//通過字節碼獲取代理類的所有普通方法
		
		System.out.println("-----獲取構造方法-----");
		for (Constructor constructor : constructors) {
			String constructorName = constructor.getName(); // 獲取構造方法的方法名
			StringBuilder sb = new StringBuilder(constructorName);
			Class[] clazzParameters = constructor.getParameterTypes(); // 獲取該構造方法的所有參數類型
			sb.append("(");
			for (Class clazzParameter : clazzParameters) {
				sb.append(clazzParameter.getName()).append(",");
			}
			if (clazzParameters.length != 0)
				sb.deleteCharAt(sb.length() - 1);
			sb.append(")");
			System.out.println(sb);
		}
		
		System.out.println("-----獲取通方法-----");
		for (Method method : methods) {
			String methodName = method.getName();
			StringBuilder sb = new StringBuilder(methodName);
			Class[] clazzParameters = method.getParameterTypes();
			sb.append("(");
			for (Class clazzParameter : clazzParameters) {
				sb.append(clazzParameter.getName()).append(",");
			}
			if (clazzParameters.length != 0)
				sb.deleteCharAt(sb.length() - 1);
			sb.append(")");
			System.out.println(sb);
		}
	}
}

輸出結果:

-----獲取構造方法-----
$Proxy0(java.lang.reflect.InvocationHandler)
-----獲取通方法-----
add(java.lang.Object)
hashCode()
clear()
equals(java.lang.Object)
toString()
contains(java.lang.Object)
isEmpty()
addAll(java.util.Collection)
iterator()
size()
toArray([Ljava.lang.Object;)
toArray()
remove(java.lang.Object)
containsAll(java.util.Collection)
removeAll(java.util.Collection)
retainAll(java.util.Collection)
isProxyClass(java.lang.Class)
getProxyClass(java.lang.ClassLoader,[Ljava.lang.Class;)
getInvocationHandler(java.lang.Object)
newProxyInstance(java.lang.ClassLoader,[Ljava.lang.Class;,java.lang.reflect.InvocationHandler)
wait()
wait(long,int)
wait(long)
getClass()
notify()
notifyAll()

 

           2、創建動態代理類的實例對象

 

         (1)通過實現 InvocationHandler 接口創建自己的調用處理器。

         (2)通過爲 Proxy 類指定 ClassLoader 對象和一組 Interface 來創建動態代理類。

         (3)通過反射機制獲得動態代理類的構造函數。指定處理器。

         (4)通過構造函數創建 動態代理實例,構造時調用處理器對象作爲參數被傳入。

 

          編程實例:創建 Collection 的代理類實例。

public class A {

	public static void main(String[] args) throws Exception {

		Class clazzProxy = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class); // 獲取代理類的字節碼

		Constructor constructor = clazzProxy.getConstructor(InvocationHandler.class); // 獲取字節碼中的構造函數信息

		Collection proxyCollection = (Collection) constructor
				.newInstance(new InvocationHandler() { // 創建實例對象
					public Object invoke(Object proxy, Method method,
							Object[] args) throws Throwable {
						return null;
					}
				});

		System.out.println(proxyCollection); // 此時會打印出 null,因爲上面並沒有指定要代理哪個類,下面會講到
		System.out.println(proxyCollection.add("DriverKing_斌")); // 會有空指針異常,還是因爲沒有指定目標類
		proxyCollection.clear(); // 正常,因爲此代理類也實現了 Collection 接口,所以具有此方法
		System.out.println(proxyCollection.size()); // 會有空指針異常,還是因爲沒有指定目標類,返回的是上面的 return null 所以將 null 轉爲 int 不可行
	}
}

 

           上面創建代理類實例的過程步驟是:告訴編譯器目標類實現了哪些接口,是哪個加載器加載 ----> 再用反射得到構造器 ----> 用構造器的 newInstance 方法構造實例對象,並且還要給構造器一個 InvocationHandler 的實例對象。

 

           這些步驟有些複雜,其實我們可以通過一步就可以創建出動態代理類的實例。

 

           編程實例:簡化創建 Collection 的代理類實例。

public class A {

	public static void main(String[] args) throws Exception {

		Collection collection = (Collection) Proxy.newProxyInstance(
				Collection.class.getClassLoader(),
				new Class[] { Collection.class }, new InvocationHandler() {
					// 需要一個目標類,如果這個目標類在 invoke方法裏面是不行的,
					// 因爲每次調用代理類的方法,實際上是調用了目標類的方法,
					// 那麼每次就生成了一個目標類的實例
					ArrayList target = new ArrayList();

					@Override
					public Object invoke(Object proxy, Method method,
							Object[] args) throws Throwable {

						return method.invoke(target, args);
					}
				});

		collection.add("DriverKing_斌");
		System.out.println(collection.size());
		System.out.println(collection);
	}
}
// 輸出:
// 1
// [DriverKing_斌]


 

          3、分析 InvocationHandler 內部實現

 

                在我們創建一個動態代理類的實例對象時,總是要指定一個 InvocationHandler 實例對象,那麼根據經驗分析,一個構造函數指定了一個參數,那麼在這個類的內部肯定是要用到這個對象,也就是說在類的成員變量裏面一定有一個 private InvocationHandler handler; 字段。在 InvocationHandler 的實現類中是有一個方法:invoke(Object proxy, Method method, Object[] args)。這三個參數分別是:

                Object proxy -----> 調用的代理對象 

                Method method -----> 調用代理對象的哪個方法

                Object[] args -----> 這個方法傳遞的哪些參數

public class A {

	public static void main(String[] args) throws Exception {

		Collection collection = (Collection) Proxy.newProxyInstance(
				Collection.class.getClassLoader(),
				                                       // a1
				new Class[] { Collection.class }, new InvocationHandler() {
					ArrayList target = new ArrayList();

					@Override
					       // 1            // a           // b
					public Object invoke(Object proxy, Method method,
							 //c
							Object[] args) throws Throwable {
						//2
						Object retVal = method.invoke(target, args);
						        //3
						return retVal;
					}
				});

		//4                        //b1       //c1
		Object retVal = collection.add("DriverKing_斌");
		
//		4的 Object 是從1來的,1的 Object 是從3來的,3中的 Object 是從 2 來的
//		a是從 a1 來的,b 是從  b1 來的,c 是從 c1 來的
	}
}


            4、動態代理工作原理:

           


                   客戶端調用代理 -----> 代理的構造方法接受一個 InvocationHandler 對象 -----> 客戶端調用代理的各個方法 -----> 代理將各個方法的請求轉給 InvocationHandler 對象的 invoke 方法(可以在這裏面加入新的功能,比如上圖中的 log()) -----> invoke 再將相應方法分發給目標類的各個方法

 

           5、將代理類中的新增功能抽取爲對象

 

                 傳遞一個功能對象給 InvocationHandler ,然後在 invoke 方法中調用這個對象中的某個方法,就實現了動態綁定。這個功能對象就是系統功能代碼模塊,即將切面代碼以參數的形式提供。

 

           編程實例:將獲取代理提取爲方法。

public class A {

	public static void main(String[] args) throws Exception {

		// ArrayList target = new ArrayList();
		// Collection b = (Collection) getProxy(target, new ProxyTest());
		// b.add("DriverKing_斌");

		Map map = new HashMap();
		Map newMap = (Map) getProxy(map, new ProxyTest());
		newMap.put("DriverKing_斌", "King");
		
		Proxy p = (Proxy)getProxy(map, new ProxyTest());
	}

	public static Object getProxy(final Object target, final IActive active) {
		Object objProxy = Proxy.newProxyInstance(target.getClass()
				.getClassLoader(), target.getClass().getInterfaces(),
				new InvocationHandler() {

					@Override
					public Object invoke(Object proxy, Method method,
							Object[] args) throws Throwable {
						active.befordMethod(method);
						Object retVal = method.invoke(target, args);
						active.afterMethod(method);
						return retVal;
					}
				});
		return objProxy;
	}
}

interface IActive {
	void befordMethod(Method method);

	void afterMethod(Method method);
}

class ProxyTest implements IActive {

	long beginTime;

	@Override
	public void afterMethod(Method method) {
		long endTime = System.currentTimeMillis();
		System.out.println(method.getName() + " 方法結束時間:" + beginTime);
		System.out.println("用時:" + (endTime - beginTime) + " 毫秒");
	}

	@Override
	public void befordMethod(Method method) {
		beginTime = System.currentTimeMillis();
		System.out.println(method.getName() + " 方法開始時間:" + beginTime);
	}
}
// 輸出:
// add 方法開始時間:1309677944129
// add 方法結束時間:1309677944129
// 用時:0 毫秒

// put 方法開始時間:1309678419613
// put 方法結束時間:1309678419613
// 用時:1 毫秒


 

六、實現 Spring 中的可配置的 AOP 框架

 

      思路分析:

  • 工廠類 BeanFactory 負責創建目標類或代理類的實例對象,並通過配置文件實現切換。其 getBean 方法根據參數字符串返回一個相應的實例對象,如果參數字符串在配置文件中對應的類名不是 ProxyFactoryBean,則直接返回該類的實例對象,否則返回該類實例對象的 getProxy 方法返回的對象。
  • BeanFactory 的構造方法接收代表配置文件的輸入流對象,配置文件的格式如下:

         #xxx=java.util.ArrayList

         xxx=cn.driverking.ProxyFactoryBean

         xxx.target=java.util.ArrayList

         xxx.advice=cn.driverking.ProxyTest

 

         代碼:

package cn.driverking;

import java.io.InputStream;
import java.util.Properties;

public class BeanFactory {

	public BeanFactory(InputStream in) throws Exception {
		this.properties.load(in);
	}

	private Properties properties = new Properties();

	public Object getBean(String name) throws Exception {
		Object bean = null;
		String className = properties.getProperty(name);
		Class clazz = Class.forName(className);
		bean = clazz.newInstance();
		if (bean instanceof ProxyFactoryBean) {
			ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean) bean;
			IAdvice advice = (IAdvice) Class.forName(properties.getProperty(name + ".advice")).newInstance();
			Object target = Class.forName(properties.getProperty(name + ".target")).newInstance();
			proxyFactoryBean.setAdvice(advice);
			proxyFactoryBean.setTarget(target);
			return proxyFactoryBean.getProxy();
		}
		return bean;
	}
}


 

package cn.driverking;

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

public class ProxyFactoryBean {

	private IAdvice advice;
	private Object target;

	public IAdvice getAdvice() {
		return advice;
	}

	public void setAdvice(IAdvice advice) {
		this.advice = advice;
	}

	public Object getTarget() {
		return target;
	}

	public void setTarget(Object target) {
		this.target = target;
	}

	public Object getProxy() {
		Object proxy = Proxy.newProxyInstance(
				target.getClass().getClassLoader(),
				target.getClass().getInterfaces(),
				new InvocationHandler() {

					@Override
					public Object invoke(Object proxy, Method method,
							Object[] args) throws Throwable {
						advice.befordMethod(method);
						Object retVal = method.invoke(target, args);
						advice.afterMethod(method);
						return retVal;
					}
				});
		return proxy;
	}
}


 

package cn.driverking;

import java.lang.reflect.Method;

public interface IAdvice {

	void befordMethod(Method method);

	void afterMethod(Method method);

}


 

package cn.driverking;

import java.lang.reflect.Method;

public class ProxyTest implements IAdvice {

	long beginTime;

	@Override
	public void afterMethod(Method method) {
		long endTime = System.currentTimeMillis();
		System.out.println(method.getName() + " 方法結束時間:" + beginTime);
		System.out.println("用時:" + (endTime - beginTime) + " 毫秒");
	}

	@Override
	public void befordMethod(Method method) {
		beginTime = System.currentTimeMillis();
		System.out.println(method.getName() + " 方法開始時間:" + beginTime);
	}
}


 

package cn.driverking;

import java.io.InputStream;
import java.util.Collection;

public class Test {

	public static void main(String[] args)throws Exception {
		InputStream in = Test.class.getResourceAsStream("config.properties");
		Object bean = new BeanFactory(in).getBean("xxx");
		System.out.println(bean.getClass().getName());
		((Collection)bean).add("DriverKing_斌");
	}
}


 

xxx=java.util.ArrayList
#xxx=cn.driverking.ProxyFactoryBean
xxx.advice=cn.driverking.ProxyTest
xxx.target=java.util.ArrayList


 

 

發佈了54 篇原創文章 · 獲贊 6 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章