——java動態代理及AOP

代理的概念及作用

   代理模式的概念:

爲其他對象提供一種代理以控制對這個對象的訪問。

說白了就是,在一些情況下客戶不想或者不能直接引用一個對象,
而代理對象可以在客戶和目標對象之間起到中介作用,去掉客戶不能看到的內容和服務或者增添客戶需要的額外服務。

   生活中的代理:

       一,買東西可以從代理商那裏買,也可以從廠商那裏賣。

       但目的都是要得到商品。從代理商那裏買顯然更方便。

   程序中的代理:

     1,要爲已存在的多個具有相同接口的目標類的各個方法增加一些系統功能

   例如,異常處理,日誌,計算方法的運行時間,事務管理等等。。

     2,編寫一個與目標類具有相同接口的代理類,代理類的每個方法調用目標類

   的相同方法,並在調用方法時加上系統功能的代碼(參見原理圖)

  原理圖:

     3,如果採用工廠模式和配置文件的方式進行管理,則不需要修改客戶端程序,

   在配置文件中配置使用用的目標類,還是代理類。這樣可以很容易切換,

  譬如,想要日誌功能時就配置代理類,否則配置目標類,這樣,增加系統功能很容易

  以後運行一段時間後,又想去掉系統功能也很容易。

AOP(Aspect Orlented Program 即:面向方面編程,簡稱AOP):

什麼是AOP?

將一切實物都抽象的看做是多個實體的抽象體,而每個不同類型的抽象體都能夠作爲這個實物的一種實現機制的表現,
從而在業務拓展時減少對原有代碼的維護,取而代之的則是 增加->切換 的操作。

AOP的優勢:

相對於OOP,面向AOP更具有可拓展性和高維護性的優勢。

具體表現在:以往我們都以“世界萬物皆對象”的思想進行編程時,會將一切事物抽象成一個實體,並使用這個實體進行我們業務方面的拓展。

  一,系統中存在交叉業務,一個交叉業務就是要切入到系統中的一個方面:

   如下所示:

                安全    事務    日誌

StudentService-----|-------|-------|----

CurseService   -----|-------|-------|----

MiscService      -----|-------|-------|----

  二,用具體程序代碼描述交叉業務:

method  method     method

{         {    {

-----------------------------切面

。。。   。。。      。。。

-----------------------------切面

}  }    }

  三,交叉業務的編程問題即爲面向方面的編程(Aspect Orlented Program 簡稱AOP),

   AOP的目標就是要是交叉業務模塊。可以採用切面代碼移動到原始方法的周圍,這與直接在方法

   中編寫切面代碼的運行效果是一樣的。

   如下所示:

-----------------------------切面

func1 func2 func3

{ {{

... ... ...

} }}

-----------------------------切面



   四,使用代理技術正好可以解決這種問題,代理是實現AOP功能的核心和關鍵技術。



動態代理技術

   一,要爲系統中的各種接口的類增加代理功能,那將需要太多的代理類,全部採用靜態代理的方式,

是一件很麻煩的事情。

   二,JVM可以在運行時動態的生成出類的字節碼,這種動態生成的字節碼問問被用作代理類,及動態代理類。

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

   四,CGLIB庫可以動態生成一個類的子類,一個類的子類也可以用作該類的代理,所以,如果要爲一個沒有實現

接口的類生成動態代理類,那麼可以使用CGLIB庫。

   五,代理類的各種方法中通常除了藥調用目標類的相應方法和隊伍返回目標返回結果外,還可以在代理方法中的

如下四個位置加上系統功能代碼:

1,在調用目標方法之前

2,在調用目標方法之後

3,在調用目標方法前後

4,在處理目標方法異常的catch塊中

分析JVM動態生成的類

   一,創建實現了Collection接口的動態類和查看其名稱,分析Proxy.getProxyClass方法的更改參數。

   二,編碼列出動態類中的所有構造方法和參數簽名

   三,編碼列出動態類中的所有方法和參數簽名

   四,創建動態的實例對象

1,用反射獲得構造方法

2,編寫一個最簡單的InvocationHandler接口的實現類

3,調用構造方法創建動態類的實例對象,並將編寫的InvocationHandler實現類的對象傳進去。

4,打印創建的對象和調用對象的沒有返回值的方法很getClass方法,演示調用其他方法沒有返回值報出異常

5,將創建動態類的實例對象的代理改成匿名內部類的形式編寫。

讓JVM創建動態類,需要給它提供如下信息:

   三個方面:

1,生成的類中有哪些方法,通過讓其實現哪些接口的方式進行告知;

2,產生的字節碼必須有一個關聯的類加載器對象

3,生成的類中的方法代碼是怎麼樣的,也是自定義的,把我們的代碼寫在一個約定好了接口對象的方法中。

  把對象傳給它,它調用自定義的方法,相當於插入了自定義的代碼,提供執行代碼的對象就是那個InvocationHandler

  對象,它是在創建動態類的實例對象的構造方法時傳遞進去的,在上面的InvocationHandler對象的invoke得到中加了

  一些代碼,就可以看到執行代碼被調用運行了。

          也可以用Proxy.newInstance方法直接一步就創建出代理對象。

完整的實例代碼如下:

package com.itheima.proxydemos;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;

/**
 * 動態代理類的操作
 * 編寫可生成代理和插入通告的通用方法。
 * @author wuyong
 *
 */
public class ProxyTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) throws Exception {
		// TODO Auto-generated method stub
		//通過代理獲取動態生成的類的class字節碼
		Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
		System.out.println("生成代理類的名稱:" + clazzProxy1.getName());
		
		//輸出所有的可見構造函數信息
		System.out.println("--------------Constructors---------------");
		showConstructors(clazzProxy1);//輸出結果是:只有一個帶參的構造方法
		
		//輸出所有的可見方法信息
		System.out.println("--------------Methods---------------");
		showMethods(clazzProxy1);
		
		System.out.println("----------------new instance object--------------------");
		//通過獲取到的字節碼,實例化一個Collection對象
		Constructor con = clazzProxy1.getConstructor(InvocationHandler.class);
		//因爲InvocationHandler是一個接口,所有不能直接實例化,故,要定義一個其實現類
		class MyInvocationHandler implements InvocationHandler{
			@Override
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				return null;
			}
			
		}
		/**
		 * 方式一:用實現類的對象來實例化
		 */
		System.out.println("------------方式一:用實現類的對象來實例化------------");
		Collection col1 = (Collection)con.newInstance(new MyInvocationHandler());
		System.out.println(col1.toString());//結果爲空,是因爲toString()方法中的返回值是null.若果col爲空的話,則肯定會報空指針異常
		
		//可以調用無返回值的方法。因爲invoke方法返回值是null,而無返回值的方法與返回值沒關係,所以可以執行。
		col1.clear();
		System.out.println("asd");
		
		//不可以調用有返回值的方法,
		//因爲此時在調用invoke()方法時,其執行後返回的是null,而size()返回的是一個整數。
//		col1.size();//包空指針異常
		
		
		/**
		 * 方式二:用匿名內部類的方式來實例化
		 */
		/*Collection col2 = (Collection)con.newInstance(new InvocationHandler(){
			@Override
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				return null;
			}
			
		});*/
		
		/**
		 * 方式三:用Proxy類的靜態方法newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 
		 * 返回一個指定接口的代理類實例,該接口可以將方法調用指派到指定的調用處理程序。
		 * 如果將對象放置在此處,則每次執行對應的方法,就可以保證,每次都是對同一個ArrayList對象進行操作
		 */
		System.out.println("--------------測試動態代理---------------");
		ArrayList target = new ArrayList();
		Collection col3 = (Collection)getProxy(target,new ImplAdvice());
		col3.add("abc");
		col3.add("abc");
		col3.add("abc");
		System.out.println(col3.size());
		for (Object col : col3) {
			System.out.println("集合元素是:" + col);
		}
		
		System.out.println("--------------自定義類的動態代理---------------");
		ProxyClassUtil pcu = new ProxyClassUtil();
		ProxyInterface it = (ProxyInterface)getProxy(pcu, new ImplAdvice());
		it.show();
	}

	/**
	 * 可生成代理和插入通告的通用方法。
	 * @param target 要代理的目標
	 * @param advice 要代理的建議接口。其中定義一些相應的操作
	 * @return 返回目標的字節碼
	 */
	private static Object getProxy(final Object target,final Advice advice) {
		Object col3 = Proxy.newProxyInstance(
				target.getClass().getClassLoader(), //獲得指定的類加載器,一般是與接口的加載器一樣。
				
				target.getClass().getInterfaces(), //給定的接口類型
				
				new InvocationHandler() {//通過匿名內部類,生成InvocationHandler對象
					@Override
					public Object invoke(Object proxy, Method method, Object[] args)
							throws Throwable {
						//如果將對象放置在此處,則每次執行對應的方法,都會重新new 一個ArrayList對象。彼此是獨立的單獨的操作。
//						ArrayList target = new ArrayList();
						/*long startTime = System.currentTimeMillis();
						Object retVal = method.invoke(target, args);
						long endTime = System.currentTimeMillis();
						System.out.println(method.getName() + ": running time of " + (startTime - endTime));
						return retVal;*/
						
						//方法開始時的時間
//						long startTime = System.currentTimeMillis();
						advice.beforeMethod(method);
						Object retVal = method.invoke(target, args);
						advice.afterMethod(method);
						//方法結束後的時間
//						long endTime = System.currentTimeMillis();
						//方法總共用時。
//						System.out.println(method.getName() + ": running time of " + (startTime - endTime));
						return retVal;
					}
				});
		return col3;
	}

	/**
	 * 獲取,並打印出所有的構造函數名稱及其參數列表
	 * @param clazz 出入的類的字節碼
	 */
	public static void showConstructors(Class clazz) {
		//獲得所有可見的構造方法
		Constructor[] cons = clazz.getConstructors();
		//遍歷
		for (Constructor con : cons) {
			//獲得構造方法的名稱
			String name = con.getName();
			StringBuilder sBuilder = new StringBuilder(name);
			sBuilder.append("(");
			
			//獲得方法中的所有的參數列表的類型的字節碼
			Class[] clazzParams = con.getParameterTypes();
			for (Class clazzParam : clazzParams) {
				//追加參數名稱
				sBuilder.append(clazzParam.getName() + ",");
			}
			
			if (clazzParams != null && clazzParams.length != 0) {
				//去掉最後一個逗號
				sBuilder.deleteCharAt(sBuilder.length() - 1);
			}
			sBuilder.append(")");
			
			System.out.println(sBuilder);
		}
	}
	
	/**
	 * 獲取,並打印出所有的可訪問方法名稱及其參數列表
	 * @param clazz
	 */
	public static void showMethods(Class clazz) {
		//獲得所有可見方法的名稱
		Method[] meds = clazz.getMethods();
		
		//遍歷數組,獲得每一個Method對象
		for (Method med : meds) {
			//獲得方法名稱
			String name = med.getName();
			StringBuilder sBuilder = new StringBuilder(name);
			sBuilder.append("(");
			
			//獲得方法的所有參數
			Class[] clazzParams = med.getParameterTypes();
			for (Class clazzParam : clazzParams) {
				//追加方法的參數名稱
				sBuilder.append(clazzParam.getName() + ",");
			}
			if (clazzParams != null && clazzParams.length != 0) {
				//去掉最後一個逗號
				sBuilder.deleteCharAt(sBuilder.length() - 1);
			}
			sBuilder.append(")");
			System.out.println(sBuilder);
		}
	}
}


用到的自定義接口及類:


自定義的Advice接口:

package com.itheima.proxydemos;

import java.lang.reflect.Method;

/**
 * 使用代理類的提供的一個建議,規範,公告接口
 * @author wuyong
 *
 */
public interface Advice {
	void beforeMethod(Method method);
	void afterMethod(Method method);
}

Advice接口的實現類:

package com.itheima.proxydemos;

import java.lang.reflect.Method;

/**
 * 自定義公告,建議接口的實現類
 * @author wuyong
 *
 */
public class ImplAdvice implements Advice {
	long startTime;
	@Override
	public void beforeMethod(Method method) {
		System.out.println(method.getName() + "方法結束後的操作");
		long endTime = System.currentTimeMillis();
		System.out.println(method.getName() + " running time of " + (endTime - startTime));
	}

	@Override
	public void afterMethod(Method method) {
		System.out.println(method.getName() + "方法開始前的操作");
		startTime = System.currentTimeMillis();
	}

}

自定義的接口:
package com.itheima.proxydemos;

/**
 * 自定義的接口
 * @author wuyong
 *
 */
public interface ProxyInterface {
	void show();
	int show(boolean bool);
}


自定義的接口的實現類:

package com.itheima.proxydemos;


/**
 * 動態代理類的測試工具類
 * @author wuyong
 *
 */
public class ProxyClassUtil implements ProxyInterface {
	public void show() {
		
	}
	public int print(int j,String c) {
		return 0;
	}
	@Override
	public int show(boolean bool) {
		return 0;
	}
}

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