約定編程
- 本節(jié)我們完全拋開AOP的概念祖凫,先來看一個(gè)約定編程的實(shí)例。當(dāng)你弄明白了這個(gè)實(shí)例后酬凳,相信SpringAOP 的概念也就很容易理解了惠况,因?yàn)閷?shí)質(zhì)上它們是異曲同工的東西。只是這需要自己去實(shí)踐宁仔,如果僅僅是看看售滤,那么效果肯定就會(huì)相差幾個(gè)檔次。
約定
- 首先來看一個(gè)簡(jiǎn)單不需要去解釋的接口台诗,如下代碼
public interface HelloService {
public void sayHello(String name);
}
- 這個(gè)接口很簡(jiǎn)單完箩,就是定義一個(gè)sayHello的方法,其中的參數(shù)name是名字拉队,這樣就可以對(duì)該名字說hello了弊知。于是很快我們可以得到這樣的一個(gè)實(shí)現(xiàn)類,如下代碼
public class HelloServiceImpl implements HelloService {
@Override
public void sayHello(String name) {
if (name == null || name.trim() == "") {
throw new RuntimeException("parameter is null!!");
}
System.out.println("hello " + name);
}
}
- 好了粱快,這里的代碼也很簡(jiǎn)單秩彤,方法 sayHello首先判斷name是否為空。如果為空事哭,則拋出異常漫雷,告訴調(diào)用者參數(shù)為空:如果不為空,則對(duì)該名字說 hello鳍咱。
- 這樣一個(gè)幾乎就是最簡(jiǎn)單的服務(wù)寫好了降盹。下面先定義一個(gè)攔截器接口,它十分簡(jiǎn)單谤辜,只是存在幾個(gè)方法蓄坏,如下代碼
/**
* 攔截器接口
*/
public interface Interceptor {
/**
* 事前方法
* @return
*/
public boolean before();
/**
* 事后方法
*/
public void after();
/**
* 取代原有事件方法
* @param invocation 回調(diào)參數(shù)价捧,可以通過它的proceed方法,回調(diào)原有事件
* @return 原有事件返回對(duì)象
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
public Object around(Invocation invocation)throws InvocationTargetException, IllegalAccessException;
/**
* 是否返回方法涡戳。事件沒有發(fā)生異常執(zhí)行
*/
public void afterReturning();
/**
* 事后異常方法结蟋,當(dāng)事件發(fā)生異常后執(zhí)行
*/
public void afterThrowing();
/**
* 是否使用around方法取代原有方法
* @return
*/
boolean useAround();
}
- 這個(gè)接口的定義我也是設(shè)計(jì)了一番的,后面會(huì)給出約定渔彰,將這些方法織入流程中嵌屎。這里我們首先給出around方法中的參數(shù)Invocation對(duì)象的源碼,如下代碼
public class Invocation {
private Object target;
private Method method;
private Object[] params;
public Invocation(Object target, Method method, Object[] params) {
this.target = target;
this.method = method;
this.params = params;
}
/**
* 反射方法
*
* @return
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, params);
}
}
- 它沒有太多可以探討的內(nèi)容恍涂,唯一值得探討的就是proceed方法编整,它會(huì)以反射的形式去調(diào)用原有的方法。
- 接著乳丰,你可以根據(jù)攔截器(Interceptor)接口的定義開發(fā)一個(gè)屬于你自己的攔截器MyInterceptor,如下代碼
public class MyInterceptor implements Interceptor {
@Override
public boolean before() {
System.out.println("before....");
return false;
}
@Override
public void after() {
System.out.println("after....");
}
@Override
public Object around(Invocation invocation) throws InvocationTargetException, IllegalAccessException {
System.out.println("around before....");
Object obj = invocation.proceed();
System.out.println("around after....");
return obj;
}
@Override
public void afterReturning() {
System.out.println("afterReturning....");
}
@Override
public void afterThrowing() {
System.out.println("afterThrowing....");
}
@Override
public boolean useAround() {
return true;
}
}
- 這個(gè)攔截器的功能也不復(fù)雜内贮,接著就要談?wù)勎液湍愕募s定了产园。約定是本節(jié)的核心,也是Spring AOP的本質(zhì)夜郁。我先提供一個(gè)類——ProxyBean給讀者使用什燕,它有一個(gè)靜態(tài)的(static)方法:
-
public static Object getProxyBean(Object target, Interceptor interceptor)
:
-
- 這個(gè)方法的說明如下:
- 要求參數(shù)target對(duì)象存在接口,而interceptor對(duì)象則是代碼HelloService的接口對(duì)象竞端;
- 那么它將返回一個(gè)對(duì)象屎即,我們把這個(gè)返回的對(duì)象記為proxy,你可以使用target對(duì)象實(shí)現(xiàn)的接口類型對(duì)它進(jìn)行強(qiáng)制轉(zhuǎn)換事富。
- 于是你就可以使用它拿到proxy了技俐,例如,下面的代碼:
HelloService helloService = new HelloServiceImpl();
HelloService proxy = (HelloService) ProxyBean.getProxyBean(helloService, new MyInterceptor());
- 然后我再提供下面的約定统台。請(qǐng)注意雕擂,這個(gè)約定是十分重要的。
- 當(dāng)調(diào)用proxy對(duì)象的方法時(shí)贱勃,其執(zhí)行流程如下井赌。
- 1)使用proxy調(diào)用方法時(shí)會(huì)先中
- 2)如果攔截器的useAround方法返回true,則執(zhí)行攔截器的around方法贵扰,而不調(diào)用target對(duì)象對(duì)應(yīng)的方法仇穗,但around方法的參數(shù)invocation對(duì)象存在一個(gè)proceed方法,它可以調(diào)用target對(duì)象對(duì)應(yīng)的方法戚绕;
- 3)無論怎么樣纹坐,在完成之前的事情后,都會(huì)執(zhí)行攔截器的after方法舞丛;
- 4)在執(zhí)行around方法或者回調(diào)target的事件方法時(shí)恰画,可能發(fā)生異常宾茂,也可能不發(fā)生異常,也可能不發(fā)生異常拴还。如果發(fā)生異常跨晴,就執(zhí)行攔截器的afterThrowing方法,否則就執(zhí)行afterReturning方法片林。
-
下面我們?cè)儆昧鞒虉D來展示這個(gè)約定流程
- 有了這個(gè)流程圖端盆,就可以進(jìn)行約定編程了。如下代碼測(cè)試約定流程费封。
public class testProxy {
public static void main(String[] args) {
HelloService helloService = new HelloServiceImpl();
HelloService proxy = (HelloService) ProxyBean.getProxyBean(helloService, new MyInterceptor());
proxy.sayHello("zhangsan");
System.out.println("########### name is null! ###########");
proxy.sayHello(null);
}
}
- 按照我們的約定焕妙,這段代碼打印的信息如下:
before....
around before....
hello zhangsan
around after....
after....
afterReturning....
########### name is null! ###########
before....
around before....
after....
afterThrowing....
ProxyBean的實(shí)現(xiàn)
-
如何將服務(wù)類和攔截方法織入對(duì)應(yīng)的流程,是ProxyBean要實(shí)現(xiàn)的功能弓摘。首先要理解動(dòng)態(tài)代理模式焚鹊。其實(shí)代理很簡(jiǎn)單,例如韧献,當(dāng)你需要采訪一名兒童時(shí)末患,首先需要經(jīng)過它父母的同意,在一些問題上父母也許會(huì)替他回答锤窑,而對(duì)于另一些問題璧针,也許父母覺得不太合適這個(gè)小孩會(huì)拒絕掉,顯然這時(shí)父母就是這名兒童的代理(proxy)了渊啰。通過代理可以增強(qiáng)或者控制對(duì)兒童這個(gè)真實(shí)對(duì)象(target)的訪問探橱,如下圖。
- 也就是需要一個(gè)代理對(duì)象绘证。在JDK中隧膏,提供了類Proxy的靜態(tài)方法——newProxyInstance,其內(nèi)容具體如下:
public static Object newProxyinstance (ClassLoader classLoader, Class<?>[] interfaces, InvocationHandler invocationHandler) throws IllegalArguπ1entException
- 給予我們來生成一個(gè)代理對(duì)象(proxy)嚷那,它有3個(gè)參數(shù):
- classLoader——類加載器私植;
- interface——綁定的接口,也就是把代理對(duì)象綁定到哪些接口下车酣,可以是多個(gè)曲稼;
- invocationHandler——綁定代理對(duì)象邏輯實(shí)現(xiàn)。
- 這里的invocationHandler是一個(gè)接口InvocationHandler對(duì)象湖员,它定義了一個(gè)invoke方法贫悄,這個(gè)方法就是實(shí)現(xiàn)代理對(duì)象的邏輯的,其定義代碼如下
/**
* 處理代理對(duì)象方法邏輯
* @param proxy 代理對(duì)象
* @param method 當(dāng)前方法
* @param args 運(yùn)行參數(shù)
* @return 方法調(diào)用結(jié)果
*/
publi Object invoke(Object proxy, Metthod method, Object[] args);
- 然后通過目標(biāo)對(duì)象(target)娘摔,方法(method)和參數(shù)(args)就能夠反射方法運(yùn)行了窄坦,于是我們可以實(shí)現(xiàn)ProxyBean的源碼如下代碼。
public class ProxyBean implements InvocationHandler {
private Object target = null;
private Interceptor interceptor = null;
/**
* 綁定代理對(duì)象
*
* @param target 被代理對(duì)象
* @param interceptor 攔截器
* @return 代理對(duì)象
*/
public static Object getProxyBean(Object target, Interceptor interceptor) {
ProxyBean proxyBean = new ProxyBean();
//保存被代理對(duì)象
proxyBean.target = target;
//保存攔截器
proxyBean.interceptor = interceptor;
//生成代理對(duì)象
Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
proxyBean);
//返回代理對(duì)象
return proxy;
}
/**
* 處理代理對(duì)象方法邏輯
*
* @param proxy 代理對(duì)象
* @param method 當(dāng)前方法
* @param args 運(yùn)行參數(shù)
* @return 方法調(diào)用結(jié)果
* @throws Throwable 異常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//異常標(biāo)識(shí)
boolean exceptionFlag = false;
Invocation invocation = new Invocation(target, method, args);
Object retObj = null;
try {
if (this.interceptor.before()) {
retObj = this.interceptor.around(invocation);
} else {
retObj = method.invoke(target, args);
}
} catch (Exception ex) {
//產(chǎn)生異常
exceptionFlag = true;
}
this.interceptor.after();
if (exceptionFlag) {
this.interceptor.afterThrowing();
} else {
this.interceptor.afterReturning();
}
return null;
}
}
- 首先,這個(gè)ProxyBean實(shí)現(xiàn)了接口InvocationHandler鸭津,因此就可以定義invoke方法了彤侍。其中在getBean方法中,我讓其生成了一個(gè)代理對(duì)象逆趋,并且創(chuàng)建了ProxyBean實(shí)例保存目標(biāo)對(duì)象(target)和攔截器(interceptor)盏阶,為了后面的調(diào)用做好了準(zhǔn)備,其次闻书,生成了一個(gè)代理對(duì)象名斟,而這個(gè)代理對(duì)象掛在target實(shí)現(xiàn)的接口之下,所以你可以用target對(duì)象實(shí)現(xiàn)的接口對(duì)這個(gè)代理對(duì)象實(shí)現(xiàn)強(qiáng)制轉(zhuǎn)換魄眉,并且將這個(gè)代理對(duì)的邏輯掛在ProxyBean實(shí)例之下矾削,這樣就完成了目標(biāo)對(duì)象(target)和代理對(duì)象(proxy)的綁定队腐。最后腥泥,將代理對(duì)象返回給調(diào)用者伯襟。于是在如下代碼中我們只需要通過以下兩句代碼:
HelloService helloService = new HelloServiceImpl();
//按約定獲取
HelloService proxy = (HelloService) ProxyBean.getProxyBean(helloService, new MyInterceptor());
- 就可以獲取這個(gè)代理對(duì)象了,當(dāng)我們使用它調(diào)用方法時(shí)晃择,就會(huì)進(jìn)入ProxyBean的invoke方法里冀值,而這個(gè)invoke方法就是按照上面的約定流程圖來實(shí)現(xiàn)的,這就是我們可以通過一定的規(guī)則完成約定編程的原因藕各。動(dòng)態(tài)代理的概念比較抽象,掌握不易焦除,這里建議對(duì)invok方法進(jìn)行調(diào)式激况,一步步印證它運(yùn)行的過程。編程是門實(shí)踐學(xué)科膘魄,通過自己動(dòng)手會(huì)有更加深入的理解乌逐。
總結(jié)
- 到現(xiàn)在為止,我們并沒有講述關(guān)于AOP的概念创葡,本節(jié)只是通過約定告訴讀者浙踢,只要提供一定的約定規(guī)則,按照約定編程后洛波,就可以把自己開發(fā)的代碼織入約定的流程中蹬挤。而實(shí)際上在開發(fā)中,你只需要知道框架和你的約定便可以了扫茅。在現(xiàn)實(shí)中很多框架也是這么做的爆雹,換句話說钙态,Spring AOP也是這么做的蚓挤,它可以通過與我們的約定崇呵,把對(duì)應(yīng)的方法通過動(dòng)態(tài)代理技術(shù)織入約定的流程中荒辕,這就是Spring AOP編程的本質(zhì)叠骑。所以掌握Spring AOP的根本在于掌握其對(duì)我們的約定規(guī)則,下面的部分讓我們來學(xué)習(xí)它們。