本文首發(fā)于 https://jaychen.cc
作者 jaychen
最近在學(xué) Spring,研究了下 AOP 和代理模式譬重,寫點(diǎn)心得和大家分享下拒逮。
AOP
先說下AOP,AOP 全稱 Aspect Oriented Programming臀规,面向切面編程滩援,和 OOP 一樣也是一種編程思想。AOP 出現(xiàn)的原因是為了解決 OOP 在處理 侵入性業(yè)務(wù)上的不足塔嬉。
那么玩徊,什么是侵入性業(yè)務(wù)?類似日志統(tǒng)計(jì)谨究、性能分析等就屬于侵入性業(yè)務(wù)恩袱。本來原本的業(yè)務(wù)邏輯代碼優(yōu)雅大氣,正常運(yùn)行胶哲,突然說需要在這段邏輯里面加上性能分析畔塔,于是代碼就變成了下面這個(gè)樣子
long begin = System.currentTimeMillis();
// 原本的業(yè)務(wù)
doSomething();
long end = System.currentTimeMillis();
long step = end - begin;
System.out.println("執(zhí)行花費(fèi) :" + step);
從上面的代碼看到,性能分析的業(yè)務(wù)代碼和原本的業(yè)務(wù)代碼混在了一起鸯屿,好端端的代碼就這么被糟蹋了澈吨。所以,侵入性業(yè)務(wù)必須有一個(gè)更好的解決方案碾盟,這個(gè)解決方案就是 AOP棚辽。
那么,AOP 是如何解決這類問題冰肴?
代理模式
通常屈藐,我們會(huì)使用代理模式來實(shí)現(xiàn) AOP榔组,這就意味著代理模式可以優(yōu)雅的解決侵入性業(yè)務(wù)問題。所以下面來重點(diǎn)分析下代理模式联逻。
這個(gè)是代理模式的類圖搓扯。很多人可能看不懂類圖,但是說實(shí)話有時(shí)候一圖勝千言包归,這里稍微解釋下類圖的含義锨推,尤其是類圖中存在的幾種連線符。
- 矩形代表一個(gè)類公壤,矩形內(nèi)部的信息有:類名换可,屬性和方法。
- 虛線 + 三角空心箭頭為
is=a
的關(guān)系厦幅,表示繼承沾鳄,所以上圖中TestSQL
和Performance
都實(shí)現(xiàn)IDatabase
接口。 - 實(shí)線 + 箭頭為關(guān)聯(lián)關(guān)系确憨,一般在代碼中以成員變量的形式體現(xiàn)译荞,所以上圖中
Performance
類有一個(gè)TestSQL
的成員變量。
有了類圖休弃,我們可以根據(jù)類圖直接寫出代理模式的代碼了吞歼。這里代理模式分為靜態(tài)代理和動(dòng)態(tài)代理兩種,我們分別來看下塔猾。
靜態(tài)代理
假設(shè)一個(gè)場(chǎng)景篙骡,我們需要測(cè)試一條 sql query 執(zhí)行所花費(fèi)的時(shí)間。
如果按照普通的方式桥帆,代碼邏輯應(yīng)該如下
long begin = System.currentTimeMillis();
query();
long end = System.currentTimeMillis();
long step = end - begin;
System.out.println("執(zhí)行花費(fèi) :" + step);
上面說過了医增,這種會(huì)導(dǎo)致查詢邏輯和性能測(cè)試邏輯混淆在一塊,那么來看看使用代理模式是如何解決這個(gè)問題的老虫。
代理模式叶骨,代理,意味著有一方代替另一方完成一件事祈匙。這里忽刽,我們會(huì)編寫兩個(gè)類:TestSQL
為query 執(zhí)行邏輯,Performance
為性能測(cè)試類夺欲。這里 Performance
會(huì)代替 TestSQL
去執(zhí)行 query 邏輯跪帝。
要想 Performance
能夠代替 TestSQL
執(zhí)行 query 邏輯,那么這兩個(gè)類應(yīng)該是有血緣關(guān)系的些阅,即這兩個(gè)必須實(shí)現(xiàn)同一個(gè)接口伞剑。
// 接口
public interface IDatabase {
void query();
}
public class TestSQL implements IDatabase {
@Override
public void query() {
System.out.println("執(zhí)行 query。市埋。黎泣。恕刘。");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 代理類
public class PerformanceMonitor implements IDatabase {
TestSQL sql;
public PerformanceMonitor(TestSQL sql) {
this.sql = sql;
}
@Override
public void query() {
long begin = System.currentTimeMillis();
// 業(yè)務(wù)邏輯。
sql.query();
long end = System.currentTimeMillis();
long step = end - begin;
System.out.println("執(zhí)行花費(fèi) : " + step);
}
}
// 測(cè)試代碼
public class Main {
public static void main(String[] strings) {
TestSQL sql = new TestSQL();
PerformanceMonitor performanceMonitor = new PerformanceMonitor(sql);
// 由 Performance 代替 testSQL 執(zhí)行
performanceMonitor.query();
}
}
從上面的示例代碼可以分析出來代理模式是如何運(yùn)作的抒倚,這里我們可以很明顯看出代理模式的優(yōu)越性褐着,TestSQL
的邏輯很純粹,沒有混入其他無關(guān)的業(yè)務(wù)代碼托呕。
動(dòng)態(tài)代理
回顧靜態(tài)代理的代碼含蓉,發(fā)現(xiàn)代理類 Performance
必須實(shí)現(xiàn) IDatabase
接口。如果有很多業(yè)務(wù)需要用到代理來實(shí)現(xiàn)项郊,那么每個(gè)業(yè)務(wù)都需要定義一個(gè)代理類馅扣,這會(huì)導(dǎo)致類迅速膨脹,為了避免這點(diǎn)呆抑,Java 提供了動(dòng)態(tài)代理岂嗓。
為何稱之為動(dòng)態(tài)代理,動(dòng)態(tài)代理底層是使用反射實(shí)現(xiàn)的鹊碍,是在程序運(yùn)行期間動(dòng)態(tài)的創(chuàng)建接口的實(shí)現(xiàn)。在靜態(tài)代理中食绿,我們需要在編碼的時(shí)候編寫 Performance
類實(shí)現(xiàn) IDatabase
接口侈咕。而使用動(dòng)態(tài)代理,我們不必編寫 Performance
實(shí)現(xiàn) IDatabase
接口器紧,而是 JDK 在底層通過反射技術(shù)動(dòng)態(tài)創(chuàng)建一個(gè) IDatabase
接口的實(shí)現(xiàn)耀销。
使用動(dòng)態(tài)代理需要使用到 InvocationHandler
和 Proxy
這兩個(gè)類。
// 代理類,不再實(shí)現(xiàn) IDatabase 接口铲汪,而是實(shí)現(xiàn) InvocationHandler 接口
public class Performance implements InvocationHandler {
private TestSQL sql;
public Performance(TestSQL sql) {
this.sql = sql;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long begin = System.currentTimeMillis();
// method.invoke 實(shí)際上就是調(diào)用 sql.query()
Object object = method.invoke(sql, args);
long end = System.currentTimeMillis();
long step = end - begin;
System.out.println("執(zhí)行花費(fèi) :" + step);
return object;
}
}
public class Main {
public static void main(String[] strings) {
TestSQL sql = new TestSQL();
Performance performance = new Performance(sql);
IDatabase proxy = (IDatabase) Proxy.newProxyInstance(
sql.getClass().getClassLoader(),
sql.getClass().getInterfaces(),
performance
);
proxy.query();
}
}
先來看看 newProxyInstance
函數(shù)熊尉,這個(gè)函數(shù)的作用就是用來動(dòng)態(tài)創(chuàng)建一個(gè)代理對(duì)象的類,這個(gè)函數(shù)需要三個(gè)參數(shù):
- 第一個(gè)參數(shù)為類加載器掌腰,如果不懂是什么玩意狰住,先套著模板寫,等我寫下一篇文章拯救你齿梁。
- 第二個(gè)參數(shù)為要代理的接口催植,在這個(gè)例子里面就是
IDatabase
接口。 - 第三個(gè)參數(shù)為實(shí)現(xiàn)
InvocationHandler
接口的對(duì)象勺择。
執(zhí)行 newProxyInstance
之后创南,Java 會(huì)在底層自動(dòng)生成一個(gè)代理類,其代碼大概如下:
public final class $Proxy1 extends Proxy implements IDatabase{
private InvocationHandler h;
private $Proxy1(){}
public $Proxy1(InvocationHandler h){
this.h = h;
}
public void query(){
////創(chuàng)建method對(duì)象
Method method = Subject.class.getMethod("query");
//調(diào)用了invoke方法
h.invoke(this, method, new Object[]{});
}
}
你會(huì)發(fā)現(xiàn)省核,這個(gè)類很像在靜態(tài)代理中的 Performance
類稿辙,是的,動(dòng)態(tài)代理其本質(zhì)是 Java 自動(dòng)為我們生成了一個(gè) $Proxy1
代理類气忠。在 mian
函數(shù)中 newProxyInstance
的返回值就是該類的一個(gè)實(shí)例邻储。并且赋咽,$Proxy1
中的 h
屬性就是 newProxyInstance
的第三個(gè)參數(shù)。所以芥备,當(dāng)我們?cè)?main
函數(shù)中執(zhí)行 proxy.query()
冬耿,實(shí)際上是調(diào)用 $proxy1#query
方法,進(jìn)而再調(diào)用 Performance#invoke
方法萌壳。而在 Performance#invoke
通過 Object object = method.invoke(sql, args);
調(diào)用了 TestSQL#query
方法亦镶。
回顧上面的流程,理解動(dòng)態(tài)代理的核心在于理解 Java 自動(dòng)生成的代理類袱瓮。這里還有一點(diǎn)要說明缤骨,JDK 的動(dòng)態(tài)代理有一個(gè)不足:它只能為接口創(chuàng)建代理實(shí)例。這句話體現(xiàn)在代碼上就是 newProxyInstance
的第二個(gè)參數(shù)是一個(gè)接口數(shù)組尺借。為什么會(huì)存在這個(gè)不足绊起?其實(shí)看 $Proxy1
代理類就知道了,這個(gè)由 JDK 生成的代理類需要繼承 Proxy
類燎斩,而 Java 只支持單繼承虱歪,所以就限制了 JDK 的動(dòng)態(tài)代理只能為接口創(chuàng)建代理。