1. 程式人生 > >Spring中的動態代理

Spring中的動態代理

1.Spring AOP 就是要對目標進行代理物件的建立, Spring AOP是基於動態代理的,有動態代理機制: JDK動態代理和CGLIB動態代理 

動態代理:在虛擬機器內部,執行的時候,動態生成代理類(執行時生成,runtime生成) ,並不是真正存在的類,

一般格式:

Proxy$$ (Proxy$$Customer)

靜態代理:實際存在代理類 (例如:struts2 Action的代理類 ActionProxy,struts2的攔截器)

JDK動態代理

JDK動態代理,針對目標物件的介面進行代理 ,動態生成介面的實現類 !(必須有介面)

1、 必須對介面生成代理

2、 採用Proxy類,通過newProxyInstance方法為目標建立代理物件

。對目標物件介面進行代理


          返回一個指定介面的代理類例項,該介面可以將方法呼叫指派到指定的呼叫處理程式。

該方法接收三個引數 :

  (1)目標物件類載入器

  (2)目標物件實現的介面

  (3)代理後的處理程式InvocationHandler,是代理例項的呼叫處理程式 實現的介面

方法返回:一個帶有代理類的指定呼叫處理程式的代理例項,它由指定的類載入器定義,並實現指定的介面

3、 實現InvocationHandler 介面中 invoke方法,在目標物件每個方法呼叫時,都會執行invoke

程式碼實現:

針對介面進行代理:

//介面(表示代理的目標介面)
public interface ICustomerService {
	//儲存
	public void save();
	//查詢
	public int find();
}

//實現類
public class CustomerServiceImpl implements ICustomerService{

	public void save() {
		System.out.println("客戶儲存了。。。。。");
	}

	public int find() {
		System.out.println("客戶查詢數量了。。。。。");
		return 100;
	}
}

針對介面實現動態代理:

實現jdk動態代理方式1:
public class JdkProxyFactory{
	//成員變數
	private Object target;
	//注入target目標物件
	public JdkProxyFactory(Object target) {
		this.target = target;
	}
	
	public Object getProxyObject(){
		//引數1:目標物件的類載入器
		//引數2:目標物件實現的介面
		//引數3:回撥方法物件
		/**方案一:在內部實現new InvocationHandler(),指定匿名類*/
		return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler(){

			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				//如果是儲存的方法,執行記錄日誌操作
				if(method.getName().equals("save")){
					writeLog();
				}
				//目標物件原來的方法執行
				Object object = method.invoke(target, args);//呼叫目標物件的某個方法,並且返回目標物件方法的返回值
				return object;
			}
			
		});
	}
	
	//記錄日誌
	private static void writeLog(){
		System.out.println("增強程式碼:寫日誌了。。。");
	}

}

//實現動態代理的方式2:直接使用呼叫類作為介面實現類,實現InvocationHandler介面
public class JdkProxyFactory implements InvocationHandler{
	//成員變數
	private Object target;
	//注入target
	public JdkProxyFactory(Object target) {
		this.target = target;
	}
	
	public Object getProxyObject(){
		//引數1:目標物件的類載入器
		//引數2:目標物件實現的介面
		//引數3:回撥方法物件
		/**方案三:直接使用呼叫類作為介面實現類,實現InvocationHandler介面*/
		return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
	}
	
	//記錄日誌
	private static void writeLog(){
		System.out.println("增強程式碼:寫日誌了。。。");
	}

	//引數1:代理物件
	//引數2:目標的方法物件
	//引數3:目標的方法的引數
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		//如果是儲存的方法,執行記錄日誌操作
		if(method.getName().equals("save")){
			writeLog();
		}
		//目標物件原來的方法執行
		Object object = method.invoke(target, args);//呼叫目標物件的某個方法,並且返回目標物件
		return object;
	}

}

測試:

 //目標:使用動態代理,對原來的方法進行功能增強,而無需更改原來的程式碼。
	//JDK動態代理:基於介面的(物件的型別,必須實現介面!)
	@Test
	public void testJdkProxy(){
		//target(目標物件)
		ICustomerService target = new CustomerServiceImpl();
		//例項化注入目標物件
		JdkProxyFactory jdkProxyFactory = new JdkProxyFactory(target);
		//獲取Proxy Object代理物件:基於目標物件型別的介面的型別的子型別的物件
		ICustomerService proxy = (ICustomerService)jdkProxyFactory.getProxyObject();
		//呼叫目標物件的方法
		proxy.save();
		System.out.println("————————————————————————————————————————");
		proxy.find();
	}

JDK動態代理的缺點: 只能面向介面代理,不能直接對目標類進行代理 ,如果沒有介面,則不能使用JDK代理。

CGLIB代理

Cglib代理的引入為了解決類的直接代理問題(生成代理子類),不需要介面也可以代理 !該代理方式需要相應的jar包,但不需要匯入。因為Spring core包已經包含cglib ,而且同時包含了cglib 依賴的asm的包(動態位元組碼的操作類庫)

1.針對沒有介面的類直接代理:

//沒有介面的類
public class ProductService {
	public void save() {
		System.out.println("商品儲存了。。。。。");
		
	}

	public int find() {
		System.out.println("商品查詢數量了。。。。。");
		return 99;
	}
}

2.使用cglib進行代理

public class CglibProxyFactory implements MethodInterceptor {

	private Object target;

	public CglibProxyFactory(Object target) {
		this.target = target;
	}

	public Object getProxyObject() {
		//代理物件生成器
		Enhancer enhancer = new Enhancer();
		
		//設定目標物件
		enhancer.setSuperclass(target.getClass());
		
		//設定回撥函式
		enhancer.setCallback(this);	
		
		//建立返回代理物件
		Object object = enhancer.create();
		
		return object;

	}

	
	//回撥方法(代理物件的方法)
	//引數1:代理物件
	//引數2:目標物件的方法物件
	//引數3:目標物件的方法的引數的值
	//引數4:代理物件的方法物件
	public Object intercept(Object proxy, Method method, Object[] args,
			MethodProxy methodProxy) throws Throwable {
		//如果是儲存的方法,執行記錄日誌操作
		if(method.getName().equals("save")){
			writeLog();
		}
		//目標物件原來的方法執行
		Object object = method.invoke(target, args);//呼叫目標物件的某個方法,並且返回目標物件
		return object;
	}

// 增強程式碼
	public static void writeLog() {
		System.out.println("記錄日誌");
	}

}

測試:

//cglib動態代理:可以基於類(無需實現介面)生成代理物件
	@Test
	public void testCglibProxy(){
		//target目標:
		ProductService target = new ProductService();
		//weave織入,生成proxy代理物件
		//代理工廠物件,注入目標
		CglibProxyFactory cglibProxyFactory = new CglibProxyFactory(target);
		//獲取proxy:思考:物件的型別
		//代理物件,其實是目標物件型別的子型別
		ProductService proxy=(ProductService) cglibProxyFactory.getProxyObject();
		//呼叫代理物件的方法
		proxy.save();
		System.out.println("————————————————————————————————————————");
		proxy.find();
		
	}

總結:

spring在執行期,生成動態代理物件,不需要特殊的編譯器.

spring有兩種代理方式:

    1.若目標物件實現了若干介面,spring使用JDK的java.lang.reflect.Proxy類代理。

    2.若目標物件沒有實現任何介面,spring使用CGLIB庫生成目標物件的子類。

使用該方式時需要注意:

   1.對介面建立代理優於對類建立代理,因為會產生更加鬆耦合的系統,所以spring預設是使用JDK代理。對類代理是讓遺留系統或無法實現介面的第三方類庫同樣可以得到通知,這種方式應該是備用方案。

    2.標記為final的方法不能夠被通知。spring是為目標類產生子類。任何需要被通知的方法都被複寫,將通知織入。final方法是不允許重寫的。

3.spring只支援方法連線點:不提供屬性接入點,spring的觀點是屬性攔截破壞了封裝。 面向物件的概念是物件自己處理工作,其他物件只能通過方法呼叫的得到的結果