學習代理模式内容:
★ 靜态代理、
★ 動态代理(JDK動态代理、CGLIB動态代理)、
★ 攔截器的原理和日志記錄
★ 代理總結
一、職責分離的例子---房屋租賃
1、重複
2、職責不分離
●【
陪着看房、陪着談價格、交鑰匙
】----不應該交個房東來重複做,不是他關心的重點,作為房東他隻需要關心【
簽合同、收房租
】
----解決:把這部分重複非業務重點的代碼重構抽離給
第三者
----中介
● 抽離之後,中介也會重複了呀,那怎麼辦呢?
----沒事,這是人家的責任,人家就是靠這個賺錢的。
✿ 用戶端Client----- 第三方(目的:增強Service的功能)-----服務端Service
用戶端Client----- 第三方(目的:增強Service的功能)-----服務端Service
二、代理模式 [設計模式] 中介作用
✿ 1、代理模式:用戶端直接使用的都是 代理對象
,并不知道真實對象是誰,此時代理對象可以在用戶端和真實對象之間起到 中介作用
。
代理對象
中介作用
(1)代理對象完全包含真實對象,用戶端使用的都是代理對象的方法,和真實對象沒有直接關系;
(2)代理模式的職責:把不是真實對象該做的事情從真實對象上撇開——職責清晰;
三、靜态代理
1、概念/原理:
在程式運作前就已經存在代理類的位元組碼檔案,代理對象和真實對象的關系在運作前就确定了。
2、靜态代理的實作過程:
■ 将功能封裝成一個接口, 代理類和真實類/委托類 都需要實作該接口
:
代理類和真實類/委托類 都需要實作該接口
真實類/委托類 需要實作該接口,很好了解,才具有特定的功能。
代理類 需要實作該接口,是因為這樣子才知道需要為哪些功能做代理、做增強。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0gTMx81dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5SMyIjN5ITO4MGNmRTOwYGOyYzX5UTO1ITM2IzLcVDMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.png)
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
建立動态代理對象
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
有接口-使用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
- JDK 動态代理是基于實作接口的,CGLIB 和 Javassit 是基于繼承委托類的。
- 從性能上考慮:Javassit > CGLIB > JDK Struts2 的攔截器和 Hibernate 延遲加載對象,采用的是 Javassit 的方式.
- 對接口建立代理優于對類建立代理,因為會産生更加松耦合的系統,也更
。符合面向接口程式設計規範
- 若委托對象實作了幹接口,優先選用 JDK 動态代理。 若委托對象沒有實作任何接口,使用 Javassit 和 CGLIB 動态代
☺ 6、動态代理的應用:過濾器、攔截器、日志記錄
1、過濾器Filter
2、攔截器Interceptor
- 過濾器和攔截器差不多,隻是過濾器是針對與web領域的概念,隻能針對與請求和響應做增強,離不開servlet-api.jar;而攔截器是對于整個java領域的概念,不僅可以應用到web層,還可以應用到service層。