動態代理

動態代理:


AOP(面向切面編程):

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

                                    安全      事務         日誌

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

 

CourseService   ------|----------|------------|-------------

 

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

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

method1         method2          method3

{                        {                           {

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

....            ....              ......

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

}                          }                           }


交叉業務的編程問題即爲面向方面的編程(Aspect oriented program ,簡稱AOP),AOP的目標就是要使交叉業務模塊化。

可以採用將切面代碼移動到原始方法的周圍,這與直接在方法中編寫切面代碼的運行效果是一樣的,如下所示:

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

func1         func2            func3

{                   {                     {

....            ....              ......

}                    }                     }

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

動態代理技術:
要爲系統中的各種接口的類增加代理功能,那將需要太多的代理類,全部採用靜態代理方式,
 將是一件非常麻煩的事情!寫成百上千個代理類,是不是太累!
JVM可以在運行期動態生成出類的字節碼,這種動態生成的類往往被用作代理類,即動態代理類。
JVM生成的動態類必須實現一個或多個接口,所以,JVM生成的動態類只能用作具有相同接口的目標類的代理。
CGLIB庫可以動態生成一個類的子類,一個類的子類也可以用作該類的代理,
 所以,如果要爲一個沒有實現接口的類生成動態代理類,那麼可以使用CGLIB庫。

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



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

JVM可以在運行期動態生成出類的字節碼,這種動態生成的類往往被用作代理類,即動態代理類。

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

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

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

 創建動態類的實例對象過程:

1、用反射獲得構造方法

2、編寫一個最簡單的InvocationHandler類

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

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

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

動態代理類:

public class ProxyTest
{/*
	讓jvm創建動態類及其實例對象,需要給它提供哪些信息?
三個方面:
	1.生成的類中有哪些方法,通過讓其實現哪些接口的方式進行告知;
	2.產生的類字節碼必須有個一個關聯的類加載器對象;
	3.生成的類中的方法的代碼是怎樣的,也得由我們提供。
	  把我們的代碼寫在一個約定好了接口對象的方法中,把對象傳給它,
	  它調用我的方法,即相當於插入了我的代碼。
	  提供執行代碼的對象就是那個InvocationHandler對象,
	  它是在創建動態類的實例對象的構造方法時傳遞進去的。
	  在上面的InvocationHandler對象的invoke方法中加一點代碼,
	  就可以看到這些代碼被調用運行了。*/
	public static void main(String[] args) throws Exception
	{	
		//static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) 
        // 返回代理類的 java.lang.Class 對象,並向其提供類加載器和接口數組。 
		Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
		//獲取Proxy的構造方法
		Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class);
		//構造方法:protected  Proxy(InvocationHandler h) //InvocationHandler是接口
        //使用其調用處理程序的指定值從子類(通常爲動態代理類)構建新的 Proxy 實例。
		//調用構造方法創建對象,構造方法裏的指定參數,即InvocationHandler的子類。
		class MyInvocationHander1 implements InvocationHandler
		{
			public Object invoke(Object proxy, Method method, Object[] args)throws Throwable 
			{
				return null;
			}	
		}//因爲是一次性使用,其實可以一次性定義匿名內部類。
		Collection proxy1 = (Collection)constructor.newInstance(new MyInvocationHander1());
		//打印Sop(proxy1)結果爲null,有可能是對象創建失敗,或者toString()方法,返回爲null
		//這個裏面是後者toString()方法,返回爲null
		proxy1.clear();//可以清除,即對象創建成功。因爲沒有返回值。即不報錯
		//調用調用代理對象的從Object類繼承的hashCode, equals, 或toString這幾個方法時,
	    //代理對象將調用請求轉發給InvocationHandler對象,對於其他方法,則不轉發調用請求。
		//proxy1.size();//此方法有返回值。但調用失敗,爲什麼?
		//因爲size();方法調用了invoke();invoke();返回了個unll,但size()方法需要int,所以報錯。

		
		
		//下面是一次性創建對象。替換上面,用到方法如下:
		//static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 
        // 返回一個指定接口的代理類實例,該接口可以將方法調用指派到指定的調用處理程序。 
		Collection proxy2 = (Collection)Proxy.newProxyInstance(Collection.class.getClassLoader(), 
					new Class[](Collection.class),
				new InvocationHandler()//匿名內部類
				{
					/*調用動態代理對象的時候其實都是調用的invoke方法
					Object invoke(Object proxy, Method method, Object[] args) 
					 在代理實例上處理方法調用並返回結果。*/
					 /*裏面沒有定義東西
					public Object invoke(Object proxy, Method method, Object[] args)throws Throwable 
					{
						return null;
					}
					*/
					//ArrayList target =new ArrayList();
					public Object invoke(Object proxy, Method method, Object[] args)throws Throwable 
					{
						ArrayList target =new ArrayList();
						long beginTime = System.currentTimeMillis();
						Object retVal = method.invoke(target, args);
						long endTime = System.currentTimeMillis();
						System.out.println(method.getName() + " running time of " + (endTime - beginTime));
						return retVal;
					}
				}
			);
		//外部調用動態代理對象方法:
		proxy2.add("zxx");
		proxy2.add("lhm");
		proxy2.add("bxd");
		proxy2.size();//當ArrayList在InvocationHandler子類對象的成員上的時候打印爲3,在invoke方法裏是答應爲0
		
	}
}


動態代理運行原理圖:


優化代碼(將其提取成方法,不理解就先看上面代碼):

import java.lang.reflect.Constructor;
public class ProxyTest1
{
	public static void main(String[] args) throws Exception
	{	
		final ArrayList target = new ArrayList();//創建需要傳遞的對象			
		Collection proxy3 = (Collection)getProxy(target,new MyAdvice());//創建需要傳遞的系統功能對象
		proxy3.add("zxx");
		proxy3.add("lhm");
		proxy3.add("bxd");

	//傳遞兩個對象,做成框架,加final是爲了方法裏面能訪問到,一個是正常的類對象,一個是需要添加的功能
		private static Object getProxy(final Object target,final Advice advice) {
			Object proxy3 = Proxy.newProxyInstance(
				target.getClass().getClassLoader(),
				/*new Class[]{Collection.class},*/
				target.getClass().getInterfaces(),
				new InvocationHandler(){
					public Object invoke(Object proxy, Method method, Object[] args)throws Throwable
					{				
						/*這部分抽取成:要實現的功能,比如日誌,異常。
						long beginTime = System.currentTimeMillis();
						Object retVal = method.invoke(target, args);
						long endTime = System.currentTimeMillis();*/
						//System.out.println(method.getName() + " running time of " + (endTime - beginTime));
						//return retVal;
						advice.beforeMethod(method);
						Object retVal = method.invoke(target, args);
						advice.afterMethod(method);
						return retVal;											
					}
				}
			);
		return proxy3;
		}
	}
}
import java.lang.reflect.Method;
public interface Advice //定義一個功能接口。
{
	void beforeMethod(Method method);
	void afterMethod(Method method);
}
import java.lang.reflect.Method;
public class MyAdvice implements Advice//實現此功能的類。需要傳遞的參數
{
	long beginTime = 0;
	public void afterMethod(Method method)
	{
		System.out.println("從黑馬畢業上班啦!");		
		long endTime = System.currentTimeMillis();
		System.out.println(method.getName() + " running time of " + (endTime - beginTime));
	}
	public void beforeMethod(Method method)
	{
		System.out.println("到黑馬來學習啦!");
		beginTime = System.currentTimeMillis();
	}
}

分析動態生成的類的內部代碼:

//注意:Collection程序調用objProxy.add(“abc”)方法時,涉及三要素:objProxy對象、add方法、“abc”參數,
	//即對應invoke(Object proxy, Method method, Object[] args)
Class Proxy$
{
	add(Object object) 
		{
			return handler.invoke(Object proxy, Method method, Object[] args);
		}
}	

實現類似於Spring的可配置的AOP框架:(和配置文件掛鉤)
工廠類BeanFactory負責創建目標類或代理類的實例對象,並通過配置文件實現切換。
 其getBean方法根據參數字符串返回一個相應的實例對象,如果參數字符串在配置文件中對應的類名不是ProxyFactoryBean,
 則直接返回該類的實例對象,否則,返回該類實例對象的getProxy方法返回的對象。
BeanFactory的構造方法接收代表配置文件的輸入流對象,配置文件格式如下:
 #xxx=java.util.ArrayList
 xxx=cn.itcast.ProxyFactoryBean
 xxx.target=java.util.ArrayList
 xxx.advice=cn.itcast.MyAdvice
ProxyFacotryBean充當封裝生成動態代理的工廠,需要爲工廠類提供哪些配置參數信息?
目標
通知
編寫客戶端應用:
編寫實現Advice接口的類和在配置文件中進行配置
調用BeanFactory獲取對象

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class BeanFactory //判斷是否生成代理類
{
	Properties props = new Properties();
	public BeanFactory(InputStream ips)
	{
		try {
			props.load(ips);
		} 
		catch (IOException e) 
		{
			e.printStackTrace();
		}
	}	
	public Object getBean(String name)
		{
		String className = props.getProperty(name);
		Object bean = null;
		try 
		{
			Class clazz = Class.forName(className);
			bean = clazz.newInstance();
		} 
		catch (Exception e) 
		{
			e.printStackTrace();
		} 
		if(bean instanceof ProxyFactoryBean)
		{
			Object proxy = null;
			ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean)bean;
			try 
			{
				Advice advice = (Advice)Class.forName(props.getProperty(name + ".advice")).newInstance();
				Object target = Class.forName(props.getProperty(name + ".target")).newInstance();
				proxyFactoryBean.setAdvice(advice);
				proxyFactoryBean.setTarget(target);
				proxy = proxyFactoryBean.getProxy();//調用生成代理的方法
			} 
			catch (Exception e) 
			{
				e.printStackTrace();
			}
			return proxy;
		}
		return bean;
	}
}

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyFactoryBean //代理類生成器,裏面定義了生成代理的方法。
{
	private Advice advice;//定義需要傳遞的兩個對象,Bean屬性
	private Object target;
	public Advice getAdvice() 
	{
		return advice;
	}
	public void setAdvice(Advice advice)
	{
		this.advice = advice;
	}
	public Object getTarget() 
	{
		return target;
	}
	public void setTarget(Object target)
	{
		this.target = target;
	}
	public Object getProxy()
	{
		Object proxy3 = Proxy.newProxyInstance(
				target.getClass().getClassLoader(),
				/*new Class[]{Collection.class},*/
				target.getClass().getInterfaces(),
				new InvocationHandler(){
				
					public Object invoke(Object proxy, Method method, Object[] args)throws Throwable 
					{
						advice.beforeMethod(method);
						Object retVal = method.invoke(target, args);
						advice.afterMethod(method);
						return retVal;							
					}
				}
				);
		return proxy3;
	}
}
//下面是需要用到的實例。
import java.lang.reflect.Method
public interface Advice //定義一個功能接口。
{
	void beforeMethod(Method method);
	void afterMethod(Method method);
}
import java.lang.reflect.Method;
public class MyAdvice implements Advice//實現此功能的類。需要傳遞的參數
{
	long beginTime = 0;
	public void afterMethod(Method method)
	{
		System.out.println("從黑馬畢業上班啦!");		
		long endTime = System.currentTimeMillis();
		System.out.println(method.getName() + " running time of " + (endTime - beginTime));
	}
	public void beforeMethod(Method method)
	{
		System.out.println("到黑馬來學習啦!");
		beginTime = System.currentTimeMillis();
	}
}
//(String即都是在配置文件裏配置)還要定義配置文件config.properties:傳入需要轉換的類
	#xxx=java.util.ArrayList//#表示屏蔽此對象,就是不傳入這個
	xxx=路徑.ProxyFactoryBean
	xxx.target=java.util.ArrayList
	xxx.advice=路徑.MyAdvice
//下面是測試、
import java.io.InputStream;
import java.util.Collection;
public class AopFrameworkTest
{
	public static void main(String[] args) throws Exception
	{
		InputStream ips = AopFrameworkTest.class.getResourceAsStream("config.properties");
		Object bean = new BeanFactory(ips).getBean("xxx");
		System.out.println(bean.getClass().getName());
		((Collection)bean).clear();
	}
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章