java基礎加強_代理


代理的概念:
要爲已存在的多個具有相同接口的目標類的各個方法增加一些系統功能,例如,異常處理、日誌、計算方法的運行時間、事務管理、等等,這種做法就是代理

如何使用代理:
編寫一個與目標類具有相同接口的代理類,代理類的每個方法調用目標類的相同方法,並在調用方法時加上系統功能的代碼。 

代理的作用
如果採用工廠模式和配置文件的方式進行管理,則不需要修改客戶端程序,在配置文件中配置是使用目標類、還是代理類,這樣以後很容易切換,譬如,想要日誌功能時就配置代理類,否則配置目標類,這樣,增加系統功能很容易,以後運行一段時間後,又想去掉系統功能也很容易。

代理架構圖

面向方面編程AOP(Aspect oriented program
系統中存在交叉業務,一個交叉業務就是要切入到系統中的一個方面,如下所示:
                              安全       事務         日誌
StudentService  ------|----------|------------|-------------
CourseService   ------|----------|------------|-------------
MiscService       ------|----------|------------|-------------

用具體的程序代碼描述交叉業務:
method1         method2          method3
{                      {                       { 
------------------------------------------------------切面
....            ....              ......
------------------------------------------------------切面
}                       }                       }

交叉業務的編程問題即爲面向方面的編程(Aspect oriented program ,簡稱AOP),AOP的目標就是要使交叉業務模塊化。可以採用將切面代碼移動到原始方法的周圍,這與直接在方法中編寫切面代碼的運行效果是一樣的,如下所示:
------------------------------------------------------切面
func1         func2            func3
{             {                { 
....            ....              ......
}             }                }
------------------------------------------------------切面

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

動態代理
要爲系統中的各種接口的類增加代理功能,那將需要太多的代理類,全部採用靜態代理方式,將是一件非常麻煩的事情!動態代理類的出現很好的解決了這個問題。

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

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

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

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

編寫一個程序,實現獲取動態代理類的構造方法和普通方法的名稱列表

步驟:
創建實現了Collection接口的動態類和查看其名稱,分析Proxy.getProxyClass方法的各個參數。
編碼列出動態類中的所有構造方法和參數簽名
編碼列出動態類中的所有方法和參數簽名

代碼示例:
package cn.itheima.Proxy;

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

public class ProxyTest
{

	public static void main(String[] args)
	{
		// 創建動態代理類
		Class clazzProxy = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class); 
		//獲得動態代理類的類名
		System.out.println(clazzProxy.getName()); 
		//獲取動態代理類中的構造函數
		System.out.println("--------begin constructors list-------"); 

		Constructor[] constructors = clazzProxy.getConstructors(); 

		for (Constructor constructor : constructors) 
		{ 

			String name = constructor.getName(); 
			//將名稱存入字符串容器StringBuilder中
			StringBuilder sb = new StringBuilder(name); 
	
			sb.append("("); 
	
			Class[] clazzParams = constructor.getParameterTypes(); 

			for(Class clazzParam : clazzParams) 
			{ 
	
				sb.append(clazzParam.getName()).append(","); 
	
			} 
			//排除無構造函數的情況
			if (clazzParams !=null && clazzParams.length != 0) 
			{ 
	
				sb.deleteCharAt(sb.length()-1); 
	
			} 
	
			sb.append(")"); 
	
			System.out.println(sb.toString()); 

		} 

		  //獲取動態代理類中的所有方法和接收參數的類型。 
		System.out.println("--------begin method list-------"); 

		Method [] methods = clazzProxy.getMethods(); 

		for (Method method : methods) 
		{ 

			String name = method.getName(); 
	
			StringBuilder sb =new StringBuilder(name); 
	
			sb.append("("); 
	
			Class[] clazzParams = method.getParameterTypes(); 
	
			for (Class clazzParam : clazzParams) 
			{ 
	
				sb.append(clazzParam.getName()).append(","); 
	
			} 
	
			if(clazzParams !=null && clazzParams.length != 0) 
			{ 
	
				sb.deleteCharAt(sb.length()-1); 
	
			} 
	
			sb.append(")"); 
	
			System.out.println(sb.toString()); 
		}	

	}

}

輸出結果:
$Proxy0
--------begin constructors list-------
$Proxy0(java.lang.reflect.InvocationHandler) //唯一的一個構造函數(有一個參數)
--------begin method list-------
hashCode() //以下是Collection中的方法
equals(java.lang.Object)
toString()
add(java.lang.Object)
contains(java.lang.Object)
isEmpty()
size()
toArray()
toArray([Ljava.lang.Object;)
addAll(java.util.Collection)
iterator()
remove(java.lang.Object)
clear()
containsAll(java.util.Collection)
removeAll(java.util.Collection)
retainAll(java.util.Collection)
isProxyClass(java.lang.Class)//以下是java.lang.reflect.Proxy類中的方法
getProxyClass(java.lang.ClassLoader,[Ljava.lang.Class;)
getInvocationHandler(java.lang.Object)
newProxyInstance(java.lang.ClassLoader,[Ljava.lang.Class;,java.lang.reflect.InvocationHandler)
getClass()
notify()
notifyAll()
wait()
wait(long,int)
wait(long)

注意:
動態生成的類的名字$Proxy0,最後的序號從0開始。依次遞增。

創建動態類的實例對象
用反射獲得構造方法
編寫一個最簡單的InvocationHandler類
調用構造方法創建動態類的實例對象,並將編寫的InvocationHandler類的實例對象傳進去
打印創建的對象和調用對象的沒有返回值的方法和getClass方法,演示調用其他有返回值的方法報告了異常。
將創建動態類的實例對象的代理改成匿名內部類的形式編寫,鍛鍊大家習慣匿名內部類。

package cn.itheima.Proxy;

import java.lang.reflect.InvocationHandler; 

import java.lang.reflect.Method; 

import java.lang.reflect.Proxy; 

import java.util.Collection; 

public class ProxyTest2 
{ 
	public static void main(String[] args)throws Exception 
	{ 
		//使用動態代理類創建一份字節碼文件
		Class<?> clazzProxy =
				Proxy.getProxyClass(Collection.class.getClassLoader(),  Collection.class); 
		//創建動態代理類的對象
		Collection<?> proxy = 	
				(Collection<?>)clazzProxy.getConstructor(InvocationHandler.class).newInstance( 	
			new InvocationHandler()
			{ 
				@Override 		
				public Object invoke(Object proxy,Method method,Object[] args) throws Throwable
				{ 
					return null; 
				} 
		}); 
		
		System.out.println(proxy); //結果:null 
	
		 
		proxy.clear(); //執行沒有返回值的方法,不會報告異常
		
		//proxy.size(); //執行有返回值的方法,會報告異常 	因爲newInstance()是一個空參數的方法
		
	} 
} 


總結:
讓jvm創建動態類及其實例對象的三個方面:
1.生成的類中有哪些方法,通過讓其實現哪些接口的方式進行告知;
2.產生的類字節碼必須有一個關聯的類加載器對象;
3.生成的類中的方法的代碼是怎樣的,也得由我們提供。把我們的代碼寫在一個約定好了接口對象的方法中,把對象傳給它,它調用我的方法,即相當於插入了我的代碼。提供執行代碼的對象就是那個InvocationHandler對象,它是在創建動態類的實例對象的構造方法時傳遞進去的。在上面的InvocationHandler對象的invoke方法中加一點代碼,就可以看到這些代碼被調用運行了。

Proxy.newInstance方法創建出代理對象

代碼示例:
package cn.itheima.Proxy;

import java.lang.reflect.InvocationHandler; 

import java.lang.reflect.Method; 

import java.lang.reflect.Proxy; 

import java.util.ArrayList; 

import java.util.Collection; 

public class ProxyTest3 
{ 

@SuppressWarnings("unchecked") 

	public static void main(String[] args) throws Exception 
	{ 
		@SuppressWarnings("rawtypes") 
		//使用newProxyInstance()方法創建代理類的實例對象
		Collection proxy = (Collection)Proxy.newProxyInstance( 
				
		Collection.class.getClassLoader(), new Class[] {Collection.class },
		
		new InvocationHandler() 
		{ 
			ArrayList<String> target =new ArrayList<String>(); //此段代碼放在放在成員變量位置上,可返回正確結果
			@Override 
			public Object invoke(Object proxy,Method method,Object[] args) throws Throwable 
			{ 
			
				long startTime = System.currentTimeMillis(); 
				
				Object retVal = method.invoke(target, args); 
				
				long endTime =System.currentTimeMillis(); 
				
				System.out.println(method.getName() +" running time is " 
				
				+ (endTime - startTime)); 
				
				return retVal; 
			
			} 
		
		});
		
		proxy.add("zhangsan"); 
		
		proxy.add("lisi"); 
		
		proxy.add("wangwu"); 
		
		System.out.println(proxy.size()); //結果:3 	
	}

} 

注意: 
如果作爲target的ArrayList對象放置在invoke方法內部定義,那麼每次調用代理的某個方法,都會調用invoke 方法,這樣作爲target的ArrayList對象每次都會被創建,這樣就導致最後調用proxy.size()的時候,結果爲0。將ArrayList定義在成員位置,就不會出現這種情況,最終結果也就變爲了3.

分析動態生成的類的內部代碼
動態生成的類實現了Collection接口(可以實現若干接口),生成的類有Collection接口中的所有方法和一個如下接受InvocationHandler參數的構造方法。

構造方法接受一個InvocationHandler對象,接受對象了要幹什麼用呢?該方法內部的代碼會是怎樣的呢?
$Proxy0 implements Collection
{
	InvocationHandler handler;
	public $Proxy0(InvocationHandler handler)
	{
		this.handler = handler;
	}
}

實現Collection接口的動態類中的各個方法的代碼又是怎樣的呢?InvocationHandler接口中定義的invoke方法接受的三個參數又是什麼意思?
圖解說明如下:

分析先前打印動態類的實例對象時,結果爲什麼會是null呢?
因爲實際輸出的是proxy.toString(),代理對象將調用請求轉發給invocationHandler對象,執行如下代碼:
String toString()

return handler.invoke(this,this.getClass().getMethod("toString"),null); 

返回值是null,所以輸出null;

調用有基本類型返回值的方法時爲什麼會出現NullPointerException異常?
例如:size()方法。代理類調用size()方法,將目標方法傳入invoke()方法中,就是如下代碼:
int size()

return handler.invoke(this,this.getClass().getMethod("size"),null); 
}
返回值類型是null,轉換爲int型數據就會報空指針異常

分析爲什麼動態類的實例對象的getClass()方法返回了正確結果呢?
調用代理對象的從Object類繼承的hashCode, equals, 或toString這幾個方法時,代理對象將調用請求轉發給InvocationHandler對象,對於其他方法,則不轉發調用請求。

讓動態生成的類成爲目標類的代理

動態代理的工作原理圖:


怎樣將目標類傳進去?
直接在InvocationHandler實現類中創建目標類的實例對象,可以看運行效果和加入日誌代碼,但沒有實際意義。
爲InvocationHandler實現類注入目標類的實例對象,不能採用匿名內部類的形式了。
讓匿名的InvocationHandler實現類訪問外面方法中的目標類實例對象的final類型的引用變量。

將創建代理的過程改爲一種更優雅的方式,eclipse重構出一個getProxy方法綁定接收目標同時返回代理對象,讓調用者更懶惰,更方便,調用者甚至不用接觸任何代理的API。

代碼示例:
package cn.itheima.Proxy;

import java.lang.reflect.Method;

public interface Advice
{
	void beforeMethod();
	void afterMethod(Object target,Method method);
}

package cn.itheima.Proxy;

import java.lang.reflect.Method;

public class MyAdvice implements Advice
{
	private long startTime; 
	@Override
	public void beforeMethod()
	{
		startTime = System.currentTimeMillis(); 
	}

	@Override
	public void afterMethod(Object target, Method method)
	{
		long endTime =System.currentTimeMillis(); 
		System.out.println("The target is "+target.getClass().getName()+"target調用方法:"+method.getName() +" running time is " + (endTime - startTime)); 
	}

}

package cn.itheima.Proxy;

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

public class ProxyTest4 
{
	@SuppressWarnings("unchecked") 
	public static void main(String[] args) throws Exception 
	{
			ArrayList<String> target =new ArrayList<String>(); 
			
			Collection<String> proxy = (Collection<String>) getProxy(target,new MyAdvice());
			
			proxy.add("zhangsan"); 
			
			proxy.add("lisi"); 
			
			proxy.add("wangwu"); 
			
			System.out.println(proxy.size()); 	
	}

	private static Object getProxy(final Object target,final Advice advice)
	{
		Object proxy = Proxy.newProxyInstance( 
				
		Collection.class.getClassLoader(), 
		
		new Class[] {Collection.class },
		
		new InvocationHandler() 
		{ 
			
			@Override 
			public Object invoke(Object proxy,Method method,Object[] args) throws Throwable 
			{ 
			
				advice.beforeMethod();
				
				Object retVal = method.invoke(target, args); 
				
				advice.afterMethod(target, method);
				
				return retVal; 
			
			} 
		
		});
		return proxy;
	}

}


將系統功能代碼模塊化,即將切面代碼也改爲通過參數形式提供,怎樣把要執行的系統功能代碼以參數形式提供?
把要執行的代碼裝到一個對象的某個方法裏,然後把這個對象作爲參數傳遞,接收者只要調用這個對象的方法,即等於執行了外界提供的代碼!
爲bind方法增加一個Advice參數。

實現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獲取對象

代碼示例:
package cn.itheima.Proxy;

import java.lang.reflect.Method;

public interface Advice
{
	void beforeMethod();
	void afterMethod(Object target,Method method);
}

package cn.itheima.Proxy;

import java.lang.reflect.Method;

public class MyAdvice implements Advice
{
	private long startTime; 
	@Override
	public void beforeMethod()
	{
		startTime = System.currentTimeMillis(); 
	}

	@Override
	public void afterMethod(Object target, Method method)
	{
		long endTime =System.currentTimeMillis(); 
		System.out.println("The target is "+target.getClass().getName()+"target調用方法:"+method.getName() +" running time is " + (endTime - startTime)); 
	}

}

package cn.itheima.Proxy;

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

public class ProxyFactoryBean
{
	private Advice advice;
	private Object target;
	ProxyFactoryBean(){}
	public Object getTarget()
	{
		return target;
	}
	public void setTarget(Object target)
	{
		this.target = target;
	}
	public Advice getAdvice()
	{
		return advice;
	}
	public void setAdvice(Advice advice)
	{
		this.advice = advice;
	}
	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.beforeMethod(); 

				Object retVal = method.invoke(target, args);  

				advice.afterMethod(target ,method); 

				return retVal; 

			} 
		
		});
		return proxy;
	}
}

package cn.itheima.Proxy;

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

public class BeanFactory
{
	
	private Properties prop = new Properties(); 
	public BeanFactory(InputStream ips) 
	{ 
		try 
		{ 
			prop.load(ips); 
		} 
		catch (Exception e) 
		{ 
		e.printStackTrace(); 
		} 
	} 
	public Object getBean(String name) 
	{ 
		String className = prop.getProperty(name); 
		Object bean =null; 
		try 
		{ 
			Class<?> clazz =Class.forName(className); 
			bean = clazz.newInstance(); 
		} 
		catch (Exception e) 
		{ 
			e.printStackTrace(); 
		} 
		if(bean instanceof ProxyFactoryBean) 
		{ 
		ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean) bean; 
		Object proxy = null; 
		try 
		{ 
			Advice advice = (Advice)Class.forName( 
			prop.getProperty(name + ".advice")).newInstance(); 
			Object target =Class.forName( 
			prop.getProperty(name + ".target")).newInstance(); 
			proxyFactoryBean.setAdvice(advice); 
			proxyFactoryBean.setTarget(target); 
			proxy = proxyFactoryBean.getProxy(); 
		} 
		catch (Exception e) 
		{ 
			e.printStackTrace(); 
		} 
		return proxy;
	} 
	return bean; 
	} 

} 

package cn.itheima.Proxy;

import java.io.InputStream;

public class AopFramworkTest
{

	public static void main(String[] args)
	{
		// TODO Auto-generated method stub
		InputStream ips = AopFramworkTest.class.getResourceAsStream("config.properties"); 

		Object Proxy = new BeanFactory(ips).getBean("proxy"); 

		System.out.println(Proxy.getClass().getName()); 


	}

}

config.properties配置文件內容 :


當proxy的值爲java.util.ArrayList時,程序輸出結果:java.util.ArrayList
當proxy的值爲cn.itheima.ProxyFactoryBean時,程序輸出結果:$Proxy0




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