JAVA設計模式 5【結構型】代理模式的理解與使用
阿新 • • 發佈:2020-08-01
今天要開始我們`結構型` 設計模式的學習,設計模式源於生活,還是希望能通過生活中的一些小栗子去理解學習它,而不是為了學習而學習這些東西。
### 結構型設計模式
結構型設計模式又分為
- 類 結構型
- 物件 結構型
> 前者使用物件的`繼承機制`來組織物件和類
後者採用`組合聚合` 的方式來組合物件。
## 代理模式 Proxy
理解`代理一詞` 代理表達的是:為某個物件提供一種代理,用於控制該物件的訪問,讓客戶端間接的訪問該物件,從而限制、或者增強源物件的一些特性。
### 舉個栗子
![image.png](https://file.chaobei.xyz/blogs/image_1596265616646.png_imagess)
從國內`科學SW`,訪問谷歌查閱一些資料的時候,我們肯定務必會藉助一些`代理器` 也就是通常所說的`VPN`,代理的伺服器可以幫助我們完成這些操作。
### 靜態代理
#### 畫個圖理解一下
![image.png](https://file.chaobei.xyz/blogs/image_1596265648362.png_imagess)
需要說明的地方有:
- 抽象父類或者介面:定義了這個代理可以代理的方法。比如定義了一個`SearchSubject` 實現它的子類必須要實現對應的`search()` 方法。
```java
/**
* 抽象主題,可以進行搜尋
*/
public abstract class SearchSubject {
/**
* 可以進行搜尋的操作
*/
public abstract void search();
}
```
- 真實物件:真實物件也就是具體將要`被代理的方法`,這個真實物件的方法我們要通過代理類`間接的去訪問`。
> 眾所周知,國內訪問不到Google,需要代理才行。
```java
public class Google extends SearchSubject {
@Override
public void search() {
System.out.println("Google 搜尋引擎");
}
}
```
- 代理類:也就是VPN ,幫助我們訪問`真實物件` 的某些方法,並且還可以做一些增強。比如在訪問`真實物件之前`做一些事情,之後做一些事情。
```java
/**
* VPN 代理
* 靜態代理也需要實現抽象主題
*/
public class VPNProxy extends SearchSubject {
/**
* 含有真實主題
*/
private Google google;
@Override
public void search() {
if (null == google) {
google = new Google();
}
this.before();
/**
* 呼叫真實物件的方法
*/
google.search();
this.after();
}
/**
* 增強方法
*/
public void before() {
System.out.println("VPN 開始執行。。。");
}
public void after() {
System.out.println("VPN 結束執行");
}
}
```
#### 執行呼叫代理
```java
VPNProxy proxy = new VPNProxy();
proxy.search();
------------------
VPN 開始執行。。。
Google 搜尋引擎
VPN 結束執行
```
以上就是我們要學習的第一種代理方式:**靜態代理**
### 動態代理
假設我們還需要代理一個物件呢?比如`必應` 假設`必應搜尋`我們國內訪問不到,必須使用代理的話,是不是又得重新建立兩個物件
- 真實物件`必應搜尋`
- 代理物件`必應搜尋的代理`
這就不利於我們系統的擴充套件性,假設有很多需要代理的,那豈不是寫一大堆。
> 因此,動態代理由此而生。
這裡我們使用JDK 提供的動態代理
```java
public static Object newProxyInstance(ClassLoader loader,Class>[] interfaces,InvocationHandler h){}
```
- ClassLoader 類載入器
- interfaces 載入的介面
- InvocationHandler 增強處理器以及呼叫代理的類
#### 建立一個可供實現的搜尋介面
```java
/**
* 搜尋介面
*/
public interface SearchInterface {
String search();
}
```
谷歌搜尋引擎實現了這個介面,並且將名稱作為返回值返回。
```java
public class GoogleSearch implements SearchInterface {
@Override
public String search() {
System.out.println("Google 搜尋引擎");
return "Google";
}
}
```
建立一個搜尋增強器,並且建立了兩個方法的增強,在呼叫代理之前和之後,都加入了一些方法。
```java
/**
* 搜尋處理器
*/
public class SearchHandler implements InvocationHandler {
private void before() {
System.out.println("handler start");
}
private void after() {
System.out.println("handler stop");
}
private SearchInterface obj;
public SearchHandler(SearchInterface obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
this.before();
/**
* 執行代理方法
*/
Object result = method.invoke(obj, args);
System.out.println("result=" + result.toString());
this.after();
return result;
}
}
```
建立一個動態代理工廠,將我們需要代理的介面傳入,並且傳入介面的處理類,即可實現介面的增強處理。
```java
/**
* 動態代理工廠
*/
public class ProxyFactory {
/**
* 目標物件
*/
private SearchInterface object;
private InvocationHandler handler;
public ProxyFactory(SearchInterface obj, InvocationHandler handler) {
this.object = obj;
this.handler = handler;
}
/**
* 獲取代理物件
* @return
*/
public Object getProxyObj() {
ClassLoader classLoader = object.getClass().getClassLoader();
Class>[] interfaces = object.getClass().getInterfaces();
return Proxy.newProxyInstance(classLoader, interfaces, handler);
}
}
```
建立一個具體的介面物件,傳入我們的代理工廠,並且將其處理器也同時傳入,我們就可以得到一個代理物件。
```java
SearchInterface search = new GoogleSearch();
System.out.println("1id=" + search);
InvocationHandler handler = new SearchHandler(search);
ProxyFactory factory = new ProxyFactory(search, handler);
SearchInterface google = (SearchInterface) factory.getProxyObj();
System.out.println("2id=" + google);
google.search();
-----------------
1id=impl.GoogleSearch@1b6d3586
handler start
result=impl.GoogleSearch@1b6d3586
handler stop
2id=impl.GoogleSearch@1b6d3586
handler start
Google 搜尋引擎
result=Google
handler stop
```
從上面的程式碼我們發現:
- 代理的物件與我們建立的物件有所不同
- 在生成代理物件的時候、已經執行了一遍invoke() 方法
- 通過代理物件呼叫具體方法的時候也執行了一遍invoke()
#### 老衲畫個圖
![image.png](https://file.chaobei.xyz/blogs/image_1596265677118.png_imagess)
這樣就好理解多了,代理工廠需要一個代理類、以及這個代理類的增強方法(處理器),通過代理工廠生成的代理物件,實現對物件的增強處理。
#### 動態代理的總結
- 代理類不需要實現介面,但是具體物件還是需要實現介面。
### Cglib代理
上面兩種代理,都是需要代理類、或者是具體的目標物件實現某個介面的基礎上出現的,假設沒有這個介面的顯示,我只想在某個具體的物件上加入增強的話,如何實現呢?
Cglib代理又被稱作`子類代理`,就是代理一個具體的子類
> 因為Spring 已經引入了相關的Cglib 的依賴,我們直接在Spring 的環境下進行測試。
建立一個具體的子類。沒有實現任何的介面
```java
public class BingSearch {
public void search() {
System.out.println("必應搜尋。。。");
}
}
```
建立類實現一個方法攔截器,其實名字就是這樣叫的。我們的代理物件,是通過工具類拿出來的。
```java
public class ProxyFactory implements MethodInterceptor {
//維護目標物件
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
private void before() {
System.out.println("代理類前置處理。。");
}
private void after() {
System.out.println("代理類後置處理。。");
}
public Object getProxy() {
//1.工具類
Enhancer en = new Enhancer();
//2.設定父類
en.setSuperclass(target.getClass());
//3.設定回撥函式
en.setCallback(this);
//4.建立子類(代理物件)
return en.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
/**
* 執行方法
*/
this.before();
Object result = method.invoke(target, objects);
this.after();
return result;
}
}
```
在main 方法對一個具體的類進行增強代理。
```java
public static void main(String[] args) {
ProxyFactory proxyFactory = new ProxyFactory(new BingSearch());
BingSearch bing = (BingSearch) proxyFactory.getProxy();
bing.search();
}
---------
代理類前置處理。。
必應搜尋。。。
代理類後置處理。。
```
## 小結
本節,將我們最常用的兩種代理模式進行了一些講解,其實最重要的是`JDK動態代理` 和`Cglib 具體方法代理增強`。因為大家已經擁抱Spring 的懷抱了,這兩種代理還是很重要的,Spring的AOP 切面也是一種基於動態代理的方式實現。非常好用,在Spring 宣告式事務當中,一個註解即可搞定許多冗餘的程式設計式事務,這無不歸功於 強大的`動態代理`
### 鳴謝&參考
https://www.cnblogs.com/leeego-123/p/10995975.html
### 程式碼
https://gitee.com/mrc1999/Dev-Examples
### 歡迎關注
![banner](https://file.chaobei.xyz/blogs/banner_1591192617