設計模式~代理模式


學習代理模式內容:

★ 靜態代理、

★ 動態代理(JDK動態代理、CGLIB動態代理)、

★ 攔截器的原理和日誌記錄

★ 代理總結




一、職責分離的例子---房屋租賃

1、重複

2、職責不分離

●【陪著看房、陪著談價格、交鑰匙】----不應該交個房東來重複做,不是他關心的重點,作為房東他只需要關心【簽合約、收房租】

----解決:把這部分重複非業務重點的程式碼重構抽離給第三者----中介


● 抽離之後,中介也會重複了呀,那怎麼辦呢?

----沒事,這是人家的責任,人家就是靠這個賺錢的。


✿ 客戶端Client----- 第三方(目的:增強Service的功能)-----服務端Service




二、代理模式 [設計模式] 中介作用

✿ 1、代理模式:客戶端直接使用的都是代理對象,並不知道真實對象是誰,此時代理對象可以在客戶端和真實對象之間起到中介作用。


(1)代理對象完全包含真實對象,客戶端使用的都是代理對象的方法,和真實對象沒有直接關係;

(2)代理模式的職責:把不是真實對象該做的事情從真實對象上撇開——職責清晰;




三、靜態代理

1、概念/原理:

在程式運行前就已經存在代理類的位元組碼文件,代理對象和真實對象的關係在運行前就確定了。


2、靜態代理的實現過程:

■ 將功能封裝成一個介面,代理類和真實類/委託類 都需要實現該介面:

  • 真實類/委託類 需要實現該介面,很好理解,才具有特定的功能。
  • 代理類 需要實現該介面,是因為這樣子才知道需要為哪些功能做代理、做增強。

image



3、靜態代理的優缺點:

● 優點:職責分離、安全

1,業務類只需要關注業務邏輯本身,保證了業務類的重用性。

2,把真實對象隱藏起來了,保護真實對象。


● 缺點:程式碼臃腫(一個真實對象需要對應一個代理對象)、不符合開閉原則(不方便擴展和維護)。

----一個代理類只能服務某一個業務介面。

1,代理對象的某個介面只服務於某一種類型的對象,也就是說每一個真實對象都得創建一個代理對象。

2,如果需要代理的方法很多,則要為每一種方法都進行代理處理。

3,如果介面增加一個方法,除了所有實現類需要實現這個方法外,代理類也需要實現此方法



4、總結靜態代理:

1、靜態代理需要實現某個介面(才知道要代理、增強什麼功能)

2、靜態代理要包含真實對象(真實對象是靜態代理類的對象屬性)----內部bean,不暴露給外界

3、測試,通過介面對象進行測試,直接調用的是靜態代理對象(間接調用真實對象)

【動態代理在測試時,是通過動態代理類,先獲取到代理對象,直接調用的是動態代理對象(間接調用真實對象)】

  • 獲取到代理對象:jdk提供的Proxy的方法newProxyInstance

● 詳細的程式碼:

/* 靜態代理類【需要實現介面,才知道需要為哪些功能做代理、做增強】*/
public class EmployeeServiceProxy implements IEmployeService{
	@Setter
	private IEmployeService target;//真實對象/委託對象
	@Setter
	private TransactionManager txManager;//事務管理器
	
	@Override
	public void save(Employee e) {
		txManager.open();
		try {
			target.save(e);
			txManager.commit();
		} catch (Exception e2) {
			txManager.rollback();
			e2.printStackTrace();
		}
	}
	
	@Override
	public void update(Employee e) {
		txManager.open();
		try {
			target.update(e);
			txManager.commit();
		} catch (Exception e2) {
			txManager.rollback();
			e2.printStackTrace();
		}
	}
}


/* 測試類 (先獲取代理對象)*/
@SpringJUnitConfig
public class App {
    
	@Autowired
	private IEmployeService service;

	@Test
	void testSave() throws Exception {
		Employee e = new Employee();
		e.setName("shangke");
		e.setAge(28);
        
		service.save(e);//調用介面對象【根據bean配置,實際調用的是靜態代理對象】
//         System.out.println(service);//介面對象的真實類型【根據bean配置,實際調用的是靜態代理對象】
//		  System.out.println(service.getClass());
	}
}

● 靜態代理bean對象的配置:

image




四、動態代理

1、學習動態動態代理之前的準備工作:位元組碼的動態載入

(1) 先了解一下java的編譯運行原理【java載入位元組碼原理】:

  • 編譯:將源文件 .java 文件,通過編譯器(javac 命令) 編譯成 位元組碼文件 .class 文件。【編譯得到位元組碼文件】

  • 運行:通過類載入器(以二進位流形式)把位元組碼載入進JVM,通過java解析器(java 命令) 進行運行程式。【jvm解析位元組碼文件】


(2) 如何動態創建一份位元組碼?(實現了在程式碼中動態創建一個類的能力):

  • 通過java的編譯和運行原理,可以看到:在運行時期,是jvm通過位元組碼的二進位資訊來載入類的。

​ 所以,當我們在運行時期,通過java編譯系統組織.class文件的格式和結構,生成相應的二進位數據,然後再把這個二進位數據載入轉換成對應的類。



2、靜態代理和動態代理的(原理)區別:

■ 靜態代理:(經歷了編譯和運行)

在程式運行前就已經存在代理類的位元組碼文件(因為通過了編譯階段),代理對象和真實對象的關係在運行前就確定了(因為通過了編譯階段)。

■ 動態代理:(只經歷了運行,咱通過某種手段得到的位元組碼【遵循位元組碼格式和結構】)

動態代理類是在程式運行期間由jvm通過反射等機制動態生成的,所以不存在代理類的位元組碼文件(因為沒有經歷編譯階段),代理對象和真實對象的關係是在程式運行期間才確定的。

□ 兩個原理相同點:

客戶端直接使用的都是代理對象,並不知道真實對象是誰,此時代理對象可以在客戶端和真實對象之間起到中介作用。



3、動態代理分類:JDK動態代理、CGLIB動態代理

(1)JDK動態代理:

① 使用JDK的 Proxy的newProxyInstance 創建動態代理對象
	//動態代理---獲取代理對象
	@SuppressWarnings("unchecked")
	public <T> T getProxyObject() {
		return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),//真實對象的類載入器
				target.getClass().getInterfaces(), //真實對象實現的介面
				 this);//如何做事務增強的對象【增強器】
	}	//動態代理---獲取代理對象
	@SuppressWarnings("unchecked")
	public <T> T getProxyObject() {
		return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),//真實對象的類載入器
				target.getClass().getInterfaces(), //真實對象實現的介面
				 this);//如何做事務增強的對象【增強器】
	}

● 詳細的程式碼:

/* 動態代理:事務的增強操作 */
public class TransactionMangagerAdvice implements InvocationHandler{
	@Setter
	private Object target;//真實對象/委託對象
	@Setter
	private TransactionManager txManager;//事務增強器
	
	//動態代理---獲取代理對象
	@SuppressWarnings("unchecked")
	public <T> T getProxyObject() {
		return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),//真實對象的類載入器
				target.getClass().getInterfaces(), //真實對象實現的介面
				 this);//如何做事務增強的對象【增強器】
	}

    
	//如何為真實對象的方法做增強具體操作
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		Object ret = null;
		txManager.open();
		try {
            //======================================================
			ret = method.invoke(target, args);//調用真實對象的方法
            //======================================================
			txManager.commit();
		} catch (Exception e) {
			e.printStackTrace();
			txManager.rollback();
		}
		return ret;
	}
}


/* 測試類 (先獲取代理對象)*/
@SpringJUnitConfig
public class App {
    
	@Autowired
	private TransactionMangagerAdvice advice;

	@Test
	void testSave() throws Exception {
		Employee e = new Employee();
		e.setName("shang");
		e.setAge(10);	
		e.setId(2L);
		
		//獲取代理對象
		IEmployeService proxy = advice.getProxyObject();
        //調用代理對象的保存操作
		proxy.save(e);
        
//		System.out.println(proxy);//TransactionMangagerAdvice對象的真實類型是代理對象
//		System.out.println(proxy.getClass());//對象的真實類型
	}
}



☺(2) 動態代理原理:

  • 原理和靜態代理差不多【客戶端直接使用的都是代理對象,並不知道真實對象是誰,此時代理對象可以在客戶端和真實對象之間起到中介作用。通過代理對象間接的調用真實對象的方法】

  • 只不過,動態代理的代理類,不是由我們所創建,是我們生成位元組碼對應格式和結構的二進位數據載入進虛擬機,動態生成的。

● 詳細的程式碼[通過 DynamicProxyClassGenerator 生成動態代理的位元組碼,再通過反編譯工具查看。]:

public class DynamicProxyClassGenerator {
	public static void main(String[] args) throws Exception {
		generateClassFile(EmployeeServiceImpl.class, "EmployeeServiceProxy");
	}

	public static void generateClassFile(Class targetClass, String proxyName)throws Exception {
		//根據類資訊和提供的代理類名稱,生成位元組碼
		byte[] classFile =
			ProxyGenerator.generateProxyClass(proxyName, targetClass.getInterfaces());
		String path = targetClass.getResource(".").getPath();
		System.out.println(path);
		FileOutputStream out = null;
		//保留到硬碟中
		out = new FileOutputStream(path + proxyName + ".class");
		out.write(classFile);
		out.close();
	}
}



(3) CGLIB動態代理:

  • 第三方,需要拷貝jar包【spring-frame框架已經集成了cglib動態代理】

  • 原理:繼承

image

● 詳細的程式碼:

/* cglib動態代理:事務的增強操作 [和jdk的區別在創建代理對象方式上] */
//動態代理---獲取代理對象
	@SuppressWarnings("unchecked")
	public <T> T getProxyObject() {
        Enhancer enhancer = new Enhancer();
        return (T)enhancer.create(target.getClass(), this);//創建代理對象
		//enhancer.setSuperclass(target.getClass());//將繼承哪一個類,去做增強
		//enhancer.setCallback(this);//設置增強對象【增強器】	
		//return (T)enhancer.create();//創建代理對象
	}



(4)JDK動態代理和CGLIB動態代理的區別:

■ JDK動態代理:要求 真實對象 必須要實現介面。
■ CGLIB動態代理:可以針對沒有介面.

image





☺ 五、代理的總結:

1、代理原理圖:

image



☺ 2、代理的目標/作用:為了給目標對象(真實對象)的方法做功能的增強。



☺ 3、動態代理原理:

  • 原理和靜態代理差不多【客戶端直接使用的都是代理對象,並不知道真實對象是誰,此時代理對象可以在客戶端和真實對象之間起到中介作用。通過代理對象間接的調用真實對象的方法】

  • 只不過,動態代理的代理類,不是由我們所創建,是我們生成位元組碼對應格式和結構的二進位數據載入進虛擬機,動態生成的。



4、JDK 動態代理 和 CGLIB 動態代理的總結:有介面-使用jdk,沒有介面-使用cglib

(1) JDK 動態代理:

① JAVA 動態代理是使用 java.lang.reflect 包中的 Proxy 類與 InvocationHandler 介面這兩個來完成的。

② 要使用 JDK 動態代理,委託類(真實類)必須要定義介面。

③ JDK 動態代理將會攔截所有 pubic 的方法(因為只能調用介面中定義的方法),這樣即使在介面中增加 了新的方法,不用修改程式碼也會被攔截。

④ 動態代理的最小單位是類(所有類中的方法都會被處理),如果只想攔截一部分方法,可以在 invoke 方法 中對要執行的方法名進行判斷 [判斷內容可以放到配置文件,方便後續修改和維護~]


(2) CGLIB 動態代理:

① CGLIB 可以生成委託類的子類,並重寫父類非 final 修飾符的方法。

② 要求類不能是 final 的,要攔截的方法要是非 final、非 static、非 private 的。

③ 動態代理的最小單位是類(所有類中的方法都會被處理);



5、性能和選擇 [有介面-使用jdk,沒有介面-使用cglib + 性能要求有要求-Javassit]

  • JDK 動態代理是基於實現介面的,CGLIB 和 Javassit 是基於繼承委託類的。

  • 從性能上考慮:Javassit > CGLIB > JDK Struts2 的攔截器和 Hibernate 延遲載入對象,採用的是 Javassit 的方式.

  • 對介面創建代理優於對類創建代理,因為會產生更加松耦合的系統,也更符合面向介面編程規範。

  • 若委託對象實現了干介面,優先選用 JDK 動態代理。 若委託對象沒有實現任何介面,使用 Javassit 和 CGLIB 動態代



☺ 6、動態代理的應用:過濾器、攔截器、日誌記錄

1、過濾器Filter

2、攔截器Interceptor

  • 過濾器和攔截器差不多,只是過濾器是針對與web領域的概念,只能針對與請求和響應做增強,離不開servlet-api.jar;而攔截器是對於整個java領域的概念,不僅可以應用到web層,還可以應用到service層。

3、日誌記錄log