Spring框架(五)—— Spring 框架代理模式

一、Spring 框架代理模式

1、代理模式概述

(1)代理(Proxy)是一種設計模式,提供了對目標對象另外的訪問方式,即通過代理對象訪問目標對象。這樣做的好處是:可以在目標對象實現的基礎上增強額外的功能操作,即擴展目標對象的功能。

(2)代理模式通常是通過持有目標對象的引用來實現的,爲了便於描述我們將代理模式中被代理對象稱爲目標對象,負責進行代理的對象被稱爲代理對象。
在這裏插入圖片描述

2、代理模式之靜態代理

(1)靜態代理概述
若代理類在程序運行前就已經存在,那麼這種代理方式被成爲靜態代理,這種情況下的代理類通常都是我們在Java代碼中定義的。 通常情況下,靜態代理中的代理類和委託類會實現同一接口或是派生自相同的父類。

(2)靜態代理步驟
① 新建業務層接口 ProductService 接口

public interface ProductService<E> {
	public void insertProduct() throws Exception;
	public void deleteProduct() throws Exception;
}

② 新建業務層 ProductServiceImp 實現類實現業務層接口,在業務實現類中完成業務邏輯代碼

public class ProductServiceImp<E> implements ProductService<E>{
	@Override
	public void insertProduct() throws Exception {
		System.out.println("執行業務層新增操作代碼");
	}
	@Override
	public void deleteProduct() throws Exception {
		System.out.println("執行業務層刪除操作代碼");
	}
}

③ 新建業務層代理類 ServiceProxy 實現業務層接口,在代理類中定義一個業務層接口的成員變量

public class ServiceProxy<E> implements ProductService<E>{
	private ProductService<Products> productService;
	public ServiceProxy(ProductService<Products> productService){
		this.productService = productService;
	}
	@Override
	public void insertProduct() throws Exception {
		try {
			System.out.println("執行獲取連接,打開事務的代碼");
			productService.insertProduct();
			System.out.println("執行關閉事務的代碼");
		} catch(Exception e) {
			System.out.println("執行事務回滾的代碼");
			throw e;
		} finally{
			System.out.println("關閉連接");
		}
	}

	@Override
	public void deleteProduct() throws Exception {
		try {	
			System.out.println("執行獲取連接,打開事務的代碼");
			productService.deleteProduct();
			System.out.println("執行關閉事務的代碼");
		} catch(Exception e) {
			System.out.println("執行事務回滾的代碼");
			throw e;
		} finally{
			System.out.println("關閉連接");
		}
	}
}

④ 測試類

public class Test {
	public static void main(String[] args) {
		ProductService<Products> productService = new ProductServiceImp<Products>();
		ServiceProxy<Products> proxy = new ServiceProxy<Products>(productService);
		try {
			proxy.insertProduct();
			proxy.deleteProduct();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

(3)靜態代理缺點
靜態代理適用性不強,代理對象和被代理對象之前是一種強耦合,代理對象必須知道被代理對象具體的變量或方法,從而進行調用,一旦被代理對象多起來,那就需要創建多個代理,工作量太大,同樣不好維護和管理。

3、代理模式之 JDK 動態代理

(1)JDK 動態代理概述

  • 代理類在程序運行時創建的代理方式被成爲動態代理。
  • 在JDK1.3之後加入了可協助開發的動態代理功能,不必爲特定對象與方法編寫特定的代理對象,使用動態代理,可以使得一個處理者(Handler)服務於各個對象,也就是說,這種情況下,代理類並不是在Java代碼中定義的,而是在運行時根據我們在Java代碼中的“指示”動態生成的。
  • 相比於靜態代理,動態代理的優勢在於可以很方便的對代理類的函數進行統一的處理,而不用修改每個代理類的函數。
  • 一個處理者的類設計必須實現 java.lang.reflect.InvocationHandler 接口,通過 InvocationHandler 接口實現的動態代理只能代理接口的實現類。
  • 動態代理的基本思路都是在程序運行期間動態的生成了新的 class 文件,可以理解爲是 java 根據我們的需求動態的編寫了一個類。通過該類對我們的目標對象進行代理。
    在這裏插入圖片描述
    (2)重要API
  • InvocationHandler:直譯爲調用處理器,使用該類可以封裝我們需要執行的代碼體,最後 Proxy 會根據該對象中包含的代碼體動態的生成一個新的 class 文件,該 class 文件就是我們的代理對象類。
  • Proxy:代理類,通過該類生成動態代理對象

(3)JDK 動態代理步驟:
① 新建業務層接口

public interface ProductsService {
	public void insert();
}

② 新建業務層實現類實現業務層接口,在業務實現類中完成業務邏輯代碼

public class ProductsServiceImp implements ProductsService{
	@Override
	public void insert(){
		System.out.println("正在執行新增操作");
	}
}
	```

③ 新建調用處理器封裝代理類中的方法體
```java
public class TransactionHander implements InvocationHandler{
	private Object target;
	public TransactionHander(Object target) {
		this.target = target;
	}
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		Object result = null;
		try {
			System.out.println("獲取連接打開事務");
			result = method.invoke(target, args);
			System.out.println("提交事務");
		}catch (Exception e) {
			System.out.println("事務回滾");
		}
		return result;
	}
}
  • TransactionHander 是一個實現了 InvocationHandler 接口的調用處理器類,成員變量 object 表示的是需要被代理的目標對象,核心方法 invoke 中的 method 參數表示的是目標對象執行的目標方法,args 數組表示方法執行時所需要的參數。通過 method 可以執行目標對象中的方法,在執行該方法前後都可以執行一些其他的代碼來對功能進行增強。
  • 當我們調用代理類對象的方法時,這個“調用”會轉送到invoke方法中,代理類對象作爲proxy參數傳入,參數method標識了我們具體調用的是代理類的哪個方法,args爲這個方法的參數。這樣一來,我們對代理類中的所有方法的調用都會變爲對invoke的調用,這樣我們可以在invoke方法中添加統一的處理邏輯(也可以根據method參數對不同的代理類方法做不同的處理)。

④ 使用Proxy類創建代理類對象

public class Test {
	public static void main(String[] args) {
		System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
		//創建目標對象
		ProductsService productsService = new ProductsServiceImp();
		//創建調用處理器對象
		TransactionHander handler = new TransactionHander(productsService);
		//創建代理對象
		ProductsService serviceProxy = (ProductsService) Proxy.newProxyInstance(productsService.getClass().getClassLoader(), productsService.getClass().getInterfaces(),handler);
		serviceProxy.insert();
	}
}

Proxy.newProxyInstance() 方法用於創建代理對象,其中第一個參數表示動態代理對象的class文件生成完畢之後交給哪一個類加載器來加載。通常使用目標對象類對應的加載器。第二個參數表示的是在生成class文件時這個代理類需要實現的接口,由於最後生成的代理對象必須是可以強轉爲目標對象的,所以代理對象實現的接口必須和目標對象保持一致,第三個參數是調用處理器對象,調用處理器對象封裝了代理對象執行的方法體。

⑤ 創建代理對象工廠類(可選)
JDK動態代理已經足夠優秀,可以使代理方式更加靈活更易於程序的拓展,但是美中不足的是,JDK動態代理只能代理實現了接口的類,如果要創建沒有實現接口的類,需要使用第三方提供的CGLIB代理。

3、代理模式之 CGLIB 動態代理

(1)CGLIB 動態代理概述
CGLIB(Code Generation Library)是一個開源項目,是一個強大的,高性能,高質量的Code生成類庫,它可以在運行期擴展Java類與實現Java接口,通俗說CGLIB可以在運行時動態生成字節碼。

(2)重要步驟
① 新建業務層接口

public interface CGLIBService {
	public void insert();
}

② 新建業務層實現類實現業務層接口,在業務實現類中完成業務邏輯代碼

public class CGLIBServiceImp implements CGLIBService{
	public void insert() {
		System.out.println("執行業務層新增操作");
	}
}

③ 新建方法攔截器 CGLIBInterceptor 類,通過該類的interceptor方法對目標方法進行增強處理

public class CGLIBInterceptor implements MethodInterceptor{
	private Object target;
	public Object getTarget() {
		return target;
	}
	public void setTarget(Object object) {
		this.target = object;
	}
	@Override
	public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
		Object result = null;;
		try {
			System.out.println("獲取連接,開啓事務");
			result = method.invoke(target, args);
			System.out.println("提交事務");
		} catch (Exception e) {
			System.out.println("事務回滾");
		}
		return result;
	}
}

④ 在方法攔截器 CGLIBInterceptor 類中自定義 getProxy() 方法用於生成代理對象

public Object getProxy(Object object) {
	//將目標對象保存到成員變量中
	this.target = object;
	//增強器,動態代碼生成器,創建代理對象
	Enhancer enhancer = new Enhancer();
	//設置回調對象
	enhancer.setCallback(this);
	//設置類的父類類型,其實就是目標對象的類型,生成的代理對象彙集成目標對象
	//設置目標對象的類信息,代理類彙集成目標類
	enhancer.setSuperclass(object.getClass());
	//動態生成字節碼,並返回代理對象
	return enhancer.create();
}

⑤ 新建測試類,調用方法攔截器類的 getProxy 方法生成代理對象

public class CGLIBTest {
	public static void main(String[] args) {
		CGLIBService service = new CGLIBServiceImp();
		CGLIBInterceptor interceptor = new CGLIBInterceptor();
		CGLIBService proxy = (CGLIBService) interceptor.getProxy(service);
		proxy.insert();
	}
}

二、Spring 代理模式原理區別

java 動態代理是利用反射機制生成一個實現代理接口的匿名類,在調用具體方法前調用 InvokeHandler 來處理。而 cglib 動態代理是利用 asm 開源包,對代理對象類的 class 文件加載進來,通過修改其字節碼生成子類來處理。

1、如果目標對象實現了接口,默認情況下會採用JDK的動態代理實現AOP
2、如果目標對象實現了接口,可以強制使用CGLIB實現AOP
3、如果目標對象沒有實現了接口,必須採用CGLIB庫,spring會自動在JDK動態代理和CGLIB之間轉換

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