AOP編程的底層實現機制、代理

Spring AOP是基於動態代理的,基於兩種動態代理機制: JDK動態代理和CGLIB動態代理

  • 動態代理:在虛擬機內部,運行的時候,動態生成代理類(運行時生成,runtime生成) ,並不是真正存在的類, 一般格式:Proxy$$
    (Proxy$$Customer)
  • 靜態代理:實際存在代理類 (例如:struts2 Action的代理類 ActionProxy,struts2的攔截器)

1.JDK動態代理

JDK動態代理,針對目標對象的接口進行代理 ,動態生成接口的實現類 !(必須有接口

//接口(表示代理的目標接口)
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動態代理

方案一:在內部實現new InvocationHandler(),指定匿名類

//專門用來生成jdk的動態代理對象的-通用
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("增強代碼:寫日誌了。。。");
	}

}

方案二:傳遞內部類的對象,指定內部類

//專門用來生成jdk的動態代理對象的-通用
public class JdkProxyFactory{
	//成員變量
	private Object target;
	//注入target
	public JdkProxyFactory(Object target) {
		this.target = target;
	}
	
	public Object getProxyObject(){
		//參數1:目標對象的類加載器
		//參數2:目標對象實現的接口
		//參數3:回調方法對象
		/**方案二:傳遞內部類的對象,指定內部類*/
		return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),new MyInvocationHandler());
	}
	
	//自己制定內部類:類的內部可以多次使用類型
	private class MyInvocationHandler implements 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("增強代碼:寫日誌了。。。");
	}

}

方案三:直接使用調用類作爲接口實現類,實現InvocationHandler接口

//專門用來生成jdk的動態代理對象的-通用
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;
	}

}

使用SpringTest.java進行測試

//目標:使用動態代理,對原來的方法進行功能增強,而無需更改原來的代碼。
//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();
}

我們要注意有這麼一段:
Object object = method.invoke(target, args);//調用目標對象的某個方法,所以執行目標對象方法的this依舊是目標對象


2.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動態代理工廠:用來生成cglib代理對象

public class CglibProxyFactory implements MethodInterceptor{
	//聲明一個代理對象引用
	private Object target;
	//注入代理對象
	public CglibProxyFactory(Object target) {
		this.target = target;
	}
	
	//獲取代理對象
	public Object getProxyObject(){
		//1.代理對象生成器(工廠思想)
		Enhancer enhancer = new Enhancer();
		//2.在增強器上設置兩個屬性
		//設置要生成代理對象的目標對象:生成的目標對象類型的子類型
		enhancer.setSuperclass(target.getClass());
		//設置回調方法
		enhancer.setCallback(this);
//		Callback
		//3.創建獲取對象
		return enhancer.create();
	}

	
	//回調方法(代理對象的方法)
	//參數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;
	}
	
	//寫日誌的增強功能
	//Advice通知、增強
	//記錄日誌
	private 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();
}

代理知識小結

區別:

  • Jdk代理:基於接口的代理,一定是基於接口,會生成目標對象的接口類型的子對象。
  • Cglib代理:基於類的代理,不需要基於接口,會生成目標對象類型的子對象。

代理知識總結:

  • spring在運行期,生成動態代理對象,不需要特殊的編譯器.
  • spring有兩種代理方式:
    1.若目標對象實現了若干接口,spring使用JDK的java.lang.reflect.Proxy類代理。
    2.若目標對象沒有實現任何接口,spring使用CGLIB庫生成目標對象的子類。
  • 使用該方式時需要注意:
    1.對接口創建代理優於對類創建代理,因爲會產生更加鬆耦合的系統,所以spring默認是使用JDK代理。
    對類代理是讓遺留系統或無法實現接口的第三方類庫同樣可以得到通知,這種方式應該是備用方案。
    2.標記爲final的方法不能夠被通知。spring是爲目標類產生子類。任何需要被通知的方法都被複寫,將通知織入。final方法是不允許重寫的。
    3.spring只支持方法連接點:不提供屬性接入點,spring的觀點是屬性攔截破壞了封裝。面向對象的概念是對象自己處理工作,其他對象只能通過方法調用的得到的結果。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章