前言
代理模式是指爲其他對象提供一種代理,以控制對這個對象的訪問
特點:代理對象在客戶端和目標對象之間起到中介的作用
代理模式(英語:Proxy Pattern)是程序設計中的一種設計模式。
所謂的代理者是指一個類別可以作爲其它東西的接口。代理者可以作任何東西的接口:網絡連接、存儲器中的大對象、文件或其它昂貴或無法複製的資源。
瞭解代理模式的同學都知道,根據代理類生成的方式,可以分爲靜態代理和動態代理。靜態代理是指開發者手動爲目標類創建一個代理類,動態代理是指根據一系列規則,由JDK或者第三方類庫動態生成代理類。
靜態代理
靜態代理相對動態代理稍微簡單一些,因爲代理類完完全全由開發者來編寫。假設一個租房場景,用靜態代理來實現
首先有一個目標類就是房東
/**
* 房東
* 目標類
* @author sicimike
* @create 2020-02-26 19:35
*/
public class Landlord {
public void signContract() {
System.out.println("房東籤合同...");
}
}
房東只需要籤合同就行了(核心服務),而其餘非核心服務都由代理來完成。
代理類就是中介或者二房東
/**
* 房東代理(中介、二房東)
* 代理類
* @author sicimike
* @create 2020-02-26 19:37
*/
public class LandlordProxy {
private Landlord landlord = new Landlord();
public void rentHouse() {
System.out.println("草擬合同...");
landlord.signContract();
System.out.println("查水錶...");
}
}
代理類的作用就是把房子租出去,包括草擬合同,查水錶等等,核心服務籤合同還是由房東來完成。但是中介不會讓你見到房東,也就是代理類實現了對目標類的訪問控制。
測試代碼
/**
* @author sicimike
* @create 2020-02-26 19:40
*/
public class StaticProxyDemo {
public static void main(String[] args) {
LandlordProxy landlordProxy = new LandlordProxy();
landlordProxy.rentHouse();
}
}
執行結果
草擬合同...
房東籤合同...
查水錶...
這樣租戶在不接觸房東的情況下,通過代理實現了租房的需求。
靜態代理就是開發者需要爲每個目標類創建一個代理類。這樣的話導致工作量劇增,並且類的數量過多,不易維護,於是便產生了動態代理。
動態代理
動態代理是指程序在運行的過程中,根據一系列的規則,爲目標類動態生成代理類。根據其實現方式不同,動態代理可以分成JDK動態代理和Cglib動態代理。
JDK動態代理
JDK是通過反射來創建代理類的,相關的類和接口主要有兩個InvocationHandler
接口和Proxy
類,先看實例
首先創建目標接口及其實現類,因爲JDK動態代理是針對接口的,所以目標類(被代理的類)必須實現接口
/**
* 租賃服務
* @author sicimike
* @create 2020-02-26 20:08
*/
public interface RentService {
public void signContract();
}
/**
* @author sicimike
* @create 2020-02-26 20:09
*/
public class RentServiceImpl implements RentService {
@Override
public void signContract() {
System.out.println("簽署合同....");
}
}
再創建實現動態代理的關鍵類,用來採集日誌
/**
* 日誌處理器
* @author sicimike
* @create 2020-02-26 20:04
*/
public class LogHandlerProxy implements InvocationHandler {
// 目標對象
private Object target;
public LogHandlerProxy(Object target) {
this.target = target;
}
public Object bind() {
Class aClass = target.getClass();
return Proxy.newProxyInstance(aClass.getClassLoader(), aClass.getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
befor();
Object result = method.invoke(target, args);
after();
return result;
}
public void befor() {
System.out.println("日誌採集...");
}
public void after() {
System.out.println("執行成功...");
}
}
需要關注的幾點是:
- 動態代理的實現需要實現
InvocationHandler
接口,接口中只有一個方法invoke
。方法中三個參數分別表示:代理對象、要執行的方法、方法參數 - 由於動態代理所代理的類是不確定的,所以
target
爲Object
類型 - 代理對象的產生是通過
Proxy#newProxyInstance
方法,三個參數分別表示:加載目標類的類加載器、目標類中所有接口、實現了InvocationHandler
接口的類的實例
測試代碼
/**
* @author sicimike
* @create 2020-02-26 20:10
*/
public class JdkDynamicProxyDemo {
public static void main(String[] args) {
// RentService必須是接口
RentService proxy = (RentService) new LogHandlerProxy(new RentServiceImpl()).bind();
proxy.signContract();
}
}
執行結果
日誌採集...
簽署合同....
執行成功...
所以JDK動態代理是靠反射來實現的,核心類就是InvocationHandler
和Proxy
。
Cglib動態代理
Cglib是利用ASM(字節碼增強)技術來實現動態代理,也就是直接修改字節碼。根據目標類生成一個子類作爲代理類,所以目標類以及被代理的方法均不能被final修飾。
使用Cglib
需要引入相關的jar包
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.4</version>
</dependency>
Cglib
方式實現動態代理主要涉及到一個接口net.sf.cglib.proxy.MethodInterceptor
和一個類net.sf.cglib.proxy.Enhancer
。先來看個實例
首先創建目標類
/**
* 被代理的類和方法均不能被final修飾
* @author sicimike
* @create 2020-02-26 21:13
*/
public class RentServiceImpl {
public void signContract() {
System.out.println("簽署合同....");
}
}
再創建實現動態代理的關鍵類,用來採集日誌
/**
* 日誌處理器
* @author sicimike
* @create 2020-02-26 21:14
*/
public class LogHandlerProxy implements MethodInterceptor {
// 目標對象
private Object target;
public LogHandlerProxy(Object target) {
this.target = target;
}
public Object bind() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
/**
* @param object 表示要進行增強的對象
* @param method 表示攔截的方法
* @param args 數組表示參數列表,基本數據類型需要傳入其包裝類型
* @param methodProxy 用於調用父類方法
*/
@Override
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
before();
Object result = method.invoke(target, args);
after();
return result;
}
public void before() {
System.out.println("日誌採集...");
}
public void after() {
System.out.println("執行成功...");
}
}
需要關注的幾點是:
- 動態代理的實現需要實現
MethodInterceptor
接口,接口中只有一個方法intercept
。方法中四個參數已經分別在代碼中註明 - 由於動態代理所代理的類是不確定的,所以
target
爲Object
類型 - 代理對象的產生是通過
Enhancer#create
方法
測試代碼
/**
* @author sicimike
* @create 2020-02-26 21:19
*/
public class CglibDynamicProxyDemo {
public static void main(String[] args) {
RentServiceImpl proxy = (RentServiceImpl) new LogHandlerProxy(new RentServiceImpl()).bind();
proxy.signContract();
}
}
執行結果
日誌採集...
簽署合同....
執行成功...
所以Cglib動態代理是靠字節碼增強技術來實現的,核心類就是MethodInterceptor
和Enhancer
。
JDK動態代理和Cglib動態代理
- 當
Bean
有實現接口時,Spring就會使用JDK實現動態代理 - 當
Bean
沒有實現接口時,Spring使用Cglib實現動態代理 - 可以強制使用Cglib,在Spring配置中加入
<aop:aspectj-autoproxy proxy-target-class="true"/>
代理模式優點
- 代理模式能將代理對象與真實被調用的目標對象分離
- 一定程度上降低了系統的耦合度,擴展性好
- 保護目標對象
- 增強目標對象
代理模式缺點
- 代理模式會造成系統設計中類數目的增加
- 在客戶端和目標對象增加一個代理對象,會造成請求處理速度變慢
- 增加系統複雜度
源代碼
總結
代理模式是一種非常重要的設計模式,Spring核心組件AOP,就是通過動態代理來實現的。所以學好了動態代理,就學好了一半的Spring。