設計模式~代理模式
學習代理模式內容:
★ 靜態代理、
★ 動態代理(JDK動態代理、CGLIB動態代理)、
★ 攔截器的原理和日誌記錄
★ 代理總結
一、職責分離的例子---房屋租賃
1、重複
2、職責不分離
●【陪著看房、陪著談價格、交鑰匙】----不應該交個房東來重複做,不是他關心的重點,作為房東他只需要關心【簽合約、收房租】
----解決:把這部分重複非業務重點的程式碼重構抽離給第三者----中介
● 抽離之後,中介也會重複了呀,那怎麼辦呢?
----沒事,這是人家的責任,人家就是靠這個賺錢的。
✿ 客戶端Client----- 第三方(目的:增強Service的功能)-----服務端Service
二、代理模式 [設計模式] 中介作用
✿ 1、代理模式:客戶端直接使用的都是代理對象,並不知道真實對象是誰,此時代理對象可以在客戶端和真實對象之間起到中介作用。
(1)代理對象完全包含真實對象,客戶端使用的都是代理對象的方法,和真實對象沒有直接關係;
(2)代理模式的職責:把不是真實對象該做的事情從真實對象上撇開——職責清晰;
三、靜態代理
1、概念/原理:
在程式運行前就已經存在代理類的位元組碼文件,代理對象和真實對象的關係在運行前就確定了。
2、靜態代理的實現過程:
■ 將功能封裝成一個介面,代理類和真實類/委託類 都需要實現該介面:
-
真實類/委託類 需要實現該介面,很好理解,才具有特定的功能。
-
代理類 需要實現該介面,是因為這樣子才知道需要為哪些功能做代理、做增強。

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對象的配置:

四、動態代理
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動態代理】
-
原理:繼承

● 詳細的程式碼:
/* 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動態代理:可以針對沒有介面.

☺ 五、代理的總結:
1、代理原理圖:

☺ 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層。

