Spring代理模式
之前提到,Spring 的兩個關鍵點就是 IoC(控制反轉) 和 AOP(面向切面編程),IoC 已經研究過了,接下里就到 AOP 了。不過在學習 Spring AOP 前,必須要了解一下代理模式,因為代理模式是 AOP 的核心。
代理模式可以分為靜態代理和動態代理,新建 Spring-08-Proxy 項目研究一下(因為在學習 Spring 的過程中,就不額外開個分類了)。
1. 靜態代理
1.1 代理模式類圖
代理模式( Proxy Pattern )是一個使用率非常高的模式,其定義如下:
Provide a surrogate or placeholder for another object to control access to it.(為其他對象提供一種代理以控制對這個對象的訪問。)
代理模式的通用類圖為

其中的三個角色為
- Subject 抽象角色:抽象主題類可以是抽象類也可以是接口,是一個最普通的業務類型定義;
- RealSubject 具體角色:也叫做被委托角色、被代理角色,是業務邏輯的具體執行者;
- Proxy 代理角色:也叫做委托類、代理類。它負責對真實角色的應用,把所有抽象主題類定義的方法限制 委托給真實主題角色實現,並且在真實主題角色處理完畢前后做預處理和善后處理工作。
1.2 租房例子
這里用租房的例子說明一下:租房本來有房東和客戶兩個對象,但房東不想自己去出租房子(或客戶找不到房東租房),就需要一個第三方房屋中介(出租代理)。
出租房子的類圖為

用代碼實現一下,首先有一個出租接口,是具體的業務
// 出租接口
// Subject:抽象角色,業務定義
public interface Rent {
void rent();
}
要出租房子的房東就要實現這個接口
// 房東角色,要出租房子
// RealSubject:業務的具體執行者
public class Host implements Rent{
public void rent() {
System.out.println("房東的房子租出去了!");
}
}
這時候客戶已經可以找房東租房了
// 我就是客戶!
public class Client {
public static void main(String[] args){
Host host = new Host();
host.rent();
}
}
// 執行結果
// 房東的房子租出去了!
不過現在房東不想自己去出租房子(或客戶找不到房東租房),就需要一個引入出租代理
// 出租代理,租房找他
// Proxy:代理角色,負責對真實角色的應用
public class RentProxy implements Rent{
// 要代理的對象
private Host host;
public RentProxy(Host host) {
this.host = host;
}
public void rent() {
// 幫房東出租房子
host.rent();
}
}
這時候客戶要想租房,就可以去找代理了
// 我就是客戶!
public class Client {
public static void main(String[] args){
// 普通代理要求不能 new 真實對象,所以這里不算
Host host = new Host();
// 房東把房子交給代理了
RentProxy rentProxy = new RentProxy(host);
// 我們找代理租房就好了
rentProxy.rent();
}
}
// 執行結果
// 房東的房子租出去了!
在這里代理的作用就是,房東把自己的房子交給代理( +Host ),客戶租房直接去找代理就行了。
這樣看來代理的作用好像不大,不過上面提到
Proxy 代理角色:也叫做委托類、代理類。它負責對真實角色的應用,把所有抽象主題類定義的方法限制 委托給真實主題角色實現,並且在真實主題角色處理完畢前后做預處理和善后處理工作。
沒錯,代理可以在業務執行前進行預處理,或者業務完成后進行善后。
讓代理來點作用,出租前先帶客戶看看房,客戶如果要租房就簽合同,代理也要收錢
// 出租代理,租房找他
// Proxy:代理角色,負責對真實角色的應用
public class RentProxy implements Rent{
// 要代理的對象
private Host host;
public RentProxy(Host host) {
this.host = host;
}
public void rent() {
// 客戶找代理租房,先帶去看房
seeHouse();
// 客戶覺得可以,簽合同
getContract();
// 幫房東出租房子
host.rent();
// 租完房子要收錢的
getCost();
}
// 預處理 before
public void seeHouse(){
System.out.println("中介帶客戶看房!");
}
// 應該算預處理
public void getContract(){
System.out.println("確認要租,簽合同了!");
}
// 善后 after
public void getCost(){
System.out.println("出租完成,收米!");
}
}
這里想到了一個問題:客戶找代理租房,是客戶跟代理說 “你帶我去看房”,“你跟我簽合同”,“你收我錢”;還是代理跟客戶說 “要租房?先去看房”,“可以就簽合同吧”,“把錢付一下” 呢?顯然應該是后者。
這里的區別就體現在,Client 中要主動調用 seeHouse、getContract、getCost 這種預處理和善后工作嗎?顯然不應該。這種工作應該讓代理去負責,客戶找到代理租房,代理直接一條龍服務!對應的就是在代理的 rent 方法中進行預處理和善后。
這時客戶去找代理租房
// 我就是客戶!
public class Client {
public static void main(String[] args){
// 普通代理要求不能 new 真實對象,所以這里不算
Host host = new Host();
// 房東把房子交給代理了
RentProxy rentProxy = new RentProxy(host);
// 我們找代理租房,簡單的租房操作其實背后有一條龍服務!
rentProxy.rent();
}
}
// 執行結果
/*
中介帶客戶看房!
確認要租,簽合同了!
房東的房子租出去了!
出租完成,收米!
*/
房東只管出租他的房子,客戶只需要找代理租房,煩人的事情都讓代理干完了,減輕了客戶和房東的負擔,這就是代理模式。
1.3 用戶業務例子
這里再用一個之前項目中的 UserService 例子加深對代理模式的理解。
首先有一個 UserService 接口,它是業務層的接口,對應 Dao 層的對數據庫的操作
// Subject 抽象角色
public interface UserService {
// 對應 Dao 層的增刪改查!
public void add();
public void delete();
public void update();
public void query();
}
然后是接口的具體實現類,之前在實現類中進行了對 Dao 層的調用,這里簡化一下(億下)
// RealSubject 真實對象
public class UserServiceImpl implements UserService{
public void add() {
System.out.println("增加用戶!");
}
public void delete() {
System.out.println("刪除用戶!");
}
public void update() {
System.out.println("修改用戶!");
}
public void query() {
System.out.println("查詢用戶!");
}
}
好了,現在的要求是,要在執行某個操作時輸出日志信息,那要怎么辦呢?
如果在 UserServiceImpl 類上修改,就是
// RealSubject 真實對象
public class UserServiceImpl implements UserService{
public void add() {
System.out.println("[Debug]:進行了Add操作");
System.out.println("增加用戶!");
}
// 同上
...
}
這樣好像完成了要求。但此時的 UserServiceImpl 類承擔了不應該由它來做的事情,一個業務實現類,為什么要輸出日志啊?這就增加了代碼的耦合性。如果要添加的奇奇怪怪的功能更多,這個業務實現類中關鍵的業務實現部分就更加不起眼了。
所以這時候就需要代理了!增加 UserServiceProxy 類,輸出日志這種事情,屬於代理的預處理或善后工作
// Proxy 角色
public class UserServiceProxy implements UserService{
// 要代理的對象!
private UserService userService;
// 不要直接 new 對象!添加 set 方法讓 Spring 注入!連起來了!
public void setUserService(UserService userService) {
this.userService = userService;
}
public void add() {
// 就當是 before 吧!
printLog("[Debug]:進行了Add操作");
userService.add();
}
public void delete() {
printLog("[Debug]:進行了Delete操作");
userService.delete();
}
public void update() {
printLog("[Debug]:進行了Update操作");
userService.update();
}
public void query() {
printLog("[Debug]:進行了Query操作");
userService.query();
}
public void printLog(String msg){
System.out.println(msg);
}
}
讓代理來輸出日志,保證了 UserServiceImpl 類的職責清晰!假設用到了增加用戶的方法
// 客戶端!
public class Client {
public static void main(String[] args){
// 這三步應該讓 Spring 來干!不過不麻煩了
UserService userService = new UserServiceImpl();
UserServiceProxy proxy = new UserServiceProxy();
proxy.setUserService(userService);
// 增加用戶
proxy.add();
}
}
// 執行結果
// [Debug]:進行了Add操作
// 增加用戶!
可以看到通過調用代理來執行業務,成功輸出了日志。而如果有其他的要求,也可以通過擴展代理類去實現!
這大概就是面向切面編程( AOP )的思想吧!

擴展:差點和裝飾模式搞混了!在裝飾模式中,裝飾類的作用就是一個特殊的代理類。
1.4 靜態代理小結
代理模式的優點
-
職責清晰:真實角色就是實現實際的業務邏輯,不用關心其他非本職責的事務,通過后期的代理完成一件事務,附帶的結果就是編程簡潔清晰。
在租房例子中體現在房東只管如何出租他房子,不用管其他亂七八糟的事情!
-
高擴展性:具體角色是隨時都會發生變化的,只要它實現了接口,無論它如何變化,代理類完全就可以在不做任何修改的情況下使用。
租房例子中的房東只要實現了出租房子接口,無論他想出租公寓還是別墅,代理都能幫他出租!
-
智能化:還沒有體現出來,這就要看動態代理了。
代理模式的缺點
- 這種代理其實是靜態代理,問題就是每有一個真實角色,就需要一個對應的代理。當真實角色很多的時候,代碼量就非常龐大了。這個問題就需要動態代理解決。
代理模式又可以擴展為普通代理和強制代理,這里先不仔細研究了,到時候繼續設計模式之禪。
2. 動態代理
什么是動態代理?
動態代理就是,在程序運行期間創建目標對象的代理對象,並對目標對象中的方法進行功能性增強的一種技術。在生成代理對象的過程中,目標對象不變,代理對象中的方法是目標對象方法的增強方法。可以理解為運行期間,對象中方法的動態攔截,在攔截方法的前后執行功能操作。
動態代理的原理比較復雜,涉及到反射和 JVM 的知識(我都不會!),所以只能先照葫蘆畫瓢先用起來。
動態代理實現的方式有兩種:JDK 和 CGLib,這里使用 JDK 實現。
2.1 代理類模板
要使用動態代理,就要用到 JDK 提供的 Proxy 類和 InvocationHandler 接口
- Proxy 類:用於生成被代理接口的代理類對象!
- InvocationHandler 接口:只有一個方法 invoke 要實現,就是這個方法去調用被代理接口的方法!
首先創建 ProxyHandler 類,這個類就是動態代理類,用它來動態地創建某個接口(實現該接口的對象)的代理類,所以要有一個 target 屬性接收要被代理的接口
// 用它來動態生成代理類!
public class ProxyHandler {
// 被代理的接口
private Object target;
// 用注入的方式!
public void setTarget(Object target) {
this.target = target;
}
}
然后要實現調用處理器 InvocationHandler 接口,讓這個類具有調用被代理接口的方法的能力(通過反射)
// 用它來動態生成代理類!
public class ProxyHandler implements InvocationHandler {
// 被代理的接口
private Object target;
// 用注入的方式!
public void setTarget(Object target) {
this.target = target;
}
// 處理代理實例,調用被代理對象的方法!
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在這里調用了被代理對象的方法!
// 就是這里進行 面向切面編程!
Object result = method.invoke(target, args);
return result;
}
}
最后要通過 Proxy 類,獲取被代理接口的代理類對象!
// 用它來動態生成代理類!
public class ProxyHandler implements InvocationHandler {
// 被代理的接口
private Object target;
// 用注入的方式!
public void setTarget(Object target) {
this.target = target;
}
// 生成代理類
public Object getProxy(){
// 用代理類,創建代理實例
// 參數為 類加載器ClassLoader loader
// 被代理的接口 Class<?>[] interfaces
// 調用處理器對象 InvocationHandler h
// 這個類就實現了調用處理器接口,所以傳自己!
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(),this);
}
// 處理代理實例,調用被代理對象的方法!
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在這里調用了被代理對象的方法!
// 就是這里進行 面向切面編程!
Object result = method.invoke(target, args);
return result;
}
}
這就是一個動態代理類的模板了,下面來使用一下。
2.2 租房動態代理
把租房例子用動態代理改寫一下!
首先,租房接口沒有變,房東也還是那個房東
public interface Rent {
void rent();
}
public class Host implements Rent{
public void rent() {
System.out.println("房東的房子租出去了!");
}
}
現在,沒有手動寫的代理類 RentProxy 了,要通過動態代理去獲取
public class Client {
@Test
public void rentTest(){
// 真實角色
Rent host = new Host();
// 代理角色,不存在!怎么辦?找動態代理類要!
ProxyHandler ph = new ProxyHandler();
// 傳入要被代理的對象
ph.setTarget(host);
// 動態生成對應的代理類!
Rent proxy = (Rent) ph.getProxy();
// 使用被代理對象的方法
proxy.rent();
}
}
// 執行結果
// 房東的房子租出去了!
我們並沒有去寫租房的代理類 RentProxy,也成功實現了代理!
不過這里沒有擴展的功能,要想進行預處理和善后工作,就要找到調用了被代理對象方法的方法——invoke方法,在調用前后增加新操作
// 用它來動態生成代理類!
public class ProxyHandler implements InvocationHandler {
...
// 處理代理實例,調用被代理對象的方法!
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在這里調用了被代理對象的方法!
seeHouse();
getContract();
Object result = method.invoke(target, args);
getCost();
return result;
}
// 預處理 before
public void seeHouse(){
System.out.println("中介帶客戶看房!");
}
// 應該算預處理
public void getContract(){
System.out.println("確認要租,簽合同了!");
}
// 善后 after
public void getCost(){
System.out.println("出租完成,收米!");
}
}
這時候客戶再去找代理租房,結果就和之前一樣了
// Client 同上
// 執行結果
/*
中介帶客戶看房!
確認要租,簽合同了!
房東的房子租出去了!
出租完成,收米!
*/
2.3 用戶業務動態代理
再把用戶業務的例子用動態代理改寫一下,要求還是加個日志!
UserService 接口和 UserServiceImpl 實現類不變,這里省略。
在動態代理類中增加擴展的日志方法,並且在 invoke 方法中使用
// 用它來動態生成代理類!
public class ProxyHandler implements InvocationHandler {
...
// 處理代理實例,調用被代理對象的方法!
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在這里調用了被代理對象的方法!
// 通過反射獲取調用的方法的名字!
printLog(method.getName());
Object result = method.invoke(target, args);
return result;
}
// 擴展功能,輸出日志
public void printLog(String msg){
System.out.println("[Debug]:進行了"+msg+"操作");
}
}
在客戶端中通過動態代理調用 add 方法
public class Client {
@Test
public void userServiceTest(){
// 真實角色
UserService userService = new UserServiceImpl();
// 找動態代理類
ProxyHandler ph = new ProxyHandler();
// 傳入要被代理的對象
ph.setTarget(userService);
// 動態生成對應的代理類!
UserService proxy = (UserService) ph.getProxy();
// 使用被代理對象的方法
proxy.add();
proxy.delete();
}
}
// 執行結果
/*
[Debug]:進行了add操作
增加用戶!
[Debug]:進行了delete操作
刪除用戶!
*/
沒有寫 UserServiceProxy 類,也進行了代理操作!在 invoke 方法中,還通過 method 的 getName 方法獲取了外部調用方法的名字( 原理還是反射😥),更加簡潔了!
2.4 動態代理小結
動態代理的入口是 Proxy 類,通過其中的 newProxyInstance 方法獲取被代理接口的代理類,參數為
ClassLoader loader
:用哪個類加載器去加載代理對象(?),通過 Class 對象中的 getClassLoader 方法獲取Class<?>[] interfaces
:被代理的接口,通過 Class 對象的 getInterfaces 方法獲取,可以獲取到 Class 對象實現的所有接口InvocationHandler h
:調用處理器對象,調用被代理接口中的方法時,會通過其中的 invoke 方法去執行;修改 invoke 方法就相當於修改了代理類
動態代理的橋梁是 InvocationHandler 接口,通過其中的 invoke 方法調用被代理接口中的方法,參數為
Object proxy
:具體的代理類對象,其中有被代理接口的方法!Method method
:被調用的方法Object[] args
:被調用方法的參數
動態代理通過 Proxy 類創建具體的代理類,代理類又通過 InvocationHandler 接口中的 invoke 方法完成對被代理接口中的方法的調用。
3. 總結
代理模式:由最簡單的靜態代理,可以擴展為普通代理和強制代理(這里沒深入),然后是最強大的動態代理!
靜態代理好是好,但要寫代理類太麻煩!所以出現了動態代理,動態代理的原理有點深奧,類加載器 Java 虛擬機啥啥的,后面再研究吧!
不過現在還不知道動態代理除了添加日志外還有什么應用場景···就像上面的租房和用戶業務,不還得對應不同的動態代理類的操作嘛?
麻了,后面再說吧😥!