前端3-Emmet語法
2. 代理模式
2.1 什麼是代理模式?
代理模式是常用的Java設計模式,它的特徵是代理類與委託類有同樣的介面,代理類主要負責為委託類預處理訊息、過濾訊息、把訊息轉發給委託類,以及事後處理訊息等。代理類與委託類之間通常會存在關聯關係,一個代理類的物件與一個委託類的物件關聯,代理類的物件本身並不真正實現服務,而是通過呼叫委託類的物件的相關方法,來提供特定的服務。按照代理類的建立時期,代理類可分為兩種。
分類:
- 靜態代理類:由程式設計師建立或由特定工具自動生成原始碼,再對其編譯。在程式執行前,代理類的.class檔案就已經存在了。
- 動態代理類:在程式執行時,運用反射機制動態建立而成。
- 還有一種動態代理CGLIB
2.2 代理模式的關鍵資訊
意圖:為其他物件提供一種代理以控制對這個物件的訪問。
主要解決:在直接訪問物件時帶來的問題,比如說:要訪問的物件在遠端的機器上。在面向物件系統中,有些物件由於某些原因(比如物件建立開銷很大,或者某些操作需要安全控制,或者需要程序外的訪問),直接訪問會給使用者或者系統結構帶來很多麻煩,我們可以在訪問此物件時加上一個對此物件的訪問層。
何時使用:想在訪問一個類時做一些控制。
如何解決:增加中間層。
關鍵程式碼:實現與被代理類組合。
應用例項:
- Windows 裡面的快捷方式。
- 買火車票不一定在火車站買,也可以去代售點。
- 一張支票或銀行存單是賬戶中資金的代理。支票在市場交易中用來代替現金,並提供對簽發人賬號上資金的控制。
- spring aop。
優點: 1、職責清晰。 2、高擴充套件性。 3、智慧化。
**靜態代理和動態代理的區別: **
- 靜態代理通常只代理一個類,動態代理是代理一個介面下的多個實現類。
- 靜態代理事先知道要代理的是什麼,而動態代理不知道要代理什麼東西,只有在執行時才知道。
- 動態代理是實現JDK裡的InvocationHandler介面的invoke方法,但注意的是代理的是介面,也就是你的業務類必須要實現介面,通過Proxy裡的newProxyInstance得到代理物件。
2.2 靜態代理
靜態代理在使用時,需要定義介面或者父類,被代理物件(即目標物件)與代理物件一起實現相同的介面或者是繼承相同父類
2.2.1 案例分析
具體要求:
1)定義一個介面: Iteacher Dao
2)目標物件 Teacherdao實現介面 Iteacher DAO
3)使用靜態代理方式,就需要在代理物件 Teacherdaoproxy中也實現 Iteacher DAO
4)呼叫的時候通過呼叫代理物件的方法來呼叫目標物件。
5)特別提醒:代理物件與目標物件要實現相同的介面,然後通過呼叫相同的方法來呼叫目標物件的方法
ITeacherDao
public interface ITeacherDao {
void teach();
}
TeacherProxyDao
public class TeacherProxyDao implements ITeacherDao {
private ITeacherDao targetTeacherDao;
public TeacherProxyDao(ITeacherDao targetTeacherDao) {
this.targetTeacherDao = targetTeacherDao;
}
public TeacherProxyDao() {
}
public void teach() {
System.out.println("代理開始。。。可以執行一些操作 eg:驗證資料的正確性等");
targetTeacherDao.teach();
System.out.println("代理結束。。。");
}
}
TeacherDao
public class TeacherDao implements ITeacherDao{
public void teach() {
System.out.println("老師授課。。。。");
}
}
測試
@Test
public void staticProxyTest(){
//獲取目標物件
TeacherDao teacherDao = new TeacherDao();
//獲取代理物件,將目標物件傳給代理物件
TeacherProxyDao teacherProxyDao = new TeacherProxyDao(teacherDao);
//使用代理物件呼叫方法
teacherProxyDao.teach();
}
2.2.2 靜態代理模式的優缺點
優點:靜態代理模式可以通過代理物件實現對目標物件功能的擴充套件,把一些繁瑣重複的功能提取出來,目標物件中只保留核心的功能
缺點:靜態代理類要在編譯時期和目標代理類實現同一個介面,這會導致代理類非常多,且一旦藉口要新增功能,代理類和目標類都需要被維護。
2.3 動態代理
2.3.1 什麼是動態代理?
動態代理也叫做:JDK 代理、介面代理。代理物件,不需要實現介面,但是目標物件要實現介面,否則不能用動態代理。代理物件的生成,是利用 JDK 的 API,動態的在記憶體中構建代理物件
2.3.2 JDK中動態代理所使用的API
-
代理類所在包:jav.lang.reflct.Proxy
-
JDK 實現代理只需要使用 newProxyInstance 方法,但是該方法需要接收三個引數,完整的寫法是:
static Object newProxyInstance(ClasLoader loader, Clas<?>[] interfaces,InvocationHandler h )
2.3.3 動態代理案例
以靜態代理為例
思路圖解:
ITeacherDao
public interface ITeacherDao {
void teach();
}
TeacherDao
public class TeacherDao implements ITeacherDao {
public void teach() {
System.out.println("老師授課。。。。");
}
}
TeacherProxyFactory
public Object getProxyInstance(){
return Proxy.newProxyInstance(targetDao.getClass().getClassLoader(), targetDao.getClass().getInterfaces()
, new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//proxy:就是返回的例項
System.out.println("代理開始。。。");
//使用反射呼叫方法
Object returnValue = method.invoke(targetDao, args);
System.out.println("代理提交。。。");
return returnValue;
}
});
}
targetDao.getClass().getInterfaces() :用JDK代理時必須讓目標類實現介面
Client
public class Client {
public static void main(String[] args) {
ITeacherDao teacherDao = new TeacherDao();
TeacherProxyFactory teacherProxyFactory = new TeacherProxyFactory(teacherDao);
ITeacherDao proxyInstance = (ITeacherDao) teacherProxyFactory.getProxyInstance();
proxyInstance.teach();
//proxyInstance -> class com.sun.proxy.$Proxy0 $表示代理物件
System.out.println("proxyInstance -> "+ proxyInstance.getClass());
}
}
2.4 cgLib代理
2.4.1 什麼是cglib代理
- 靜態代理和 JDK 代理模式都要求目標物件是實現一個介面,但是有時候目標物件只是一個單獨的物件,並沒有實
現任何的介面,這個時候可使用目標物件子類來實現代理-這就是 Cglib 代理 - Cglib代理也叫作子類代理,它是在記憶體中構建一個子類物件從而實現對目標物件功能擴充套件, 有些書也將Cglib代
理歸屬到動態代理。 - Cglib 是一個強大的高效能的程式碼生成包,它可以在執行期擴充套件 jav 類與實現 jav 介面.它廣泛的被許多 AOP 的
框架使用,例如 Spring AOP,實現方法攔截 - 在 AOP 程式設計中如何選擇代理模式:
- 目標物件需要實現介面,用 JDK 代理
- 目標物件不需要實現介面,用 Cglib 代理
- Cglib 包的底層是通過使用位元組碼處理框架 ASM 來轉換位元組碼並生成新的類
2.4.2 cgLib代理實現步驟
**案例: **對TeacherDao實現cglib代理
圖解思路
-
引入如下包
<dependencies> <!-- https://mvnrepository.com/artifact/org.ow2.asm/asm --> <dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm</artifactId> <version>7.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.ow2.asm/asm-commons --> <dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm-commons</artifactId> <version>7.1</version> </dependency> <!-- https://mvnrepository.com/artifact/org.ow2.asm/asm-tree --> <dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm-tree</artifactId> <version>8.0.1</version> </dependency> <!-- https://mvnrepository.com/artifact/cglib/cglib --> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency> </dependencies>
-
實現目標類,不需要繼承介面
public class TeacherDao { public void teach() { System.out.println("老師授課。。。。"); } }
-
建立代理工廠
- 代理工廠持有目標物件的引用
- 實現getProxyInstance方法
- 繼承MethodInterceptor介面,實現intercept方法
public class TeacherProxyFactory implements MethodInterceptor { private final TeacherDao targetDao; public TeacherProxyFactory(TeacherDao teacherDao) { this.targetDao = teacherDao; } public Object getProxyInstance(){ //建立一個工具類 Enhancer enhancer = new Enhancer(); //指定父類物件 enhancer.setSuperclass(targetDao.getClass()); //設定回撥函式 enhancer.setCallback(this); //建立目標物件的代理物件,即目標物件的子類物件 return enhancer.create(); } //反射生成的子類呼叫方法必定會被這個方法攔截,對目標物件方法的功能進行擴充套件 public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("代理開始...."); Object returnValue = method.invoke(targetDao, objects); System.out.println("代理提交。。。"); return returnValue; } }
-
實現client類
public class Client { public static void main(String[] args) { //獲取目標物件 TeacherDao teacherDao = new TeacherDao(); //獲取代理工廠物件 TeacherProxyFactory proxyFactory = new TeacherProxyFactory(teacherDao); //獲取代理物件 TeacherDao proxyInstance = (TeacherDao) proxyFactory.getProxyInstance(); proxyInstance.teach(); } }
2.4.3 cglib代理和普通的動態代理的區別
- cglib代理不需要實現介面
- cglib代理生成的物件是目標物件的子類而動態代理生成的是介面的實現類