上一篇我們簡(jiǎn)單介紹了一下AOP中的一些相關(guān)術(shù)語盖呼、以及Advice接口下的一些增強(qiáng)實(shí)現(xiàn),但是這里會(huì)有一個(gè)問題化撕,那就是增強(qiáng)方法還會(huì)被應(yīng)用到目標(biāo)類的所有接口几晤。修改一下上一節(jié)的測(cè)試類并運(yùn)行。(本篇很多簡(jiǎn)介摘自Spring3.X企業(yè)應(yīng)用開發(fā)實(shí)戰(zhàn)植阴,實(shí)在想不出來如何去介紹這些概念類的信息蟹瘾。。墙贱。)
1.Pointcut概念的引入及簡(jiǎn)介
@Test
public void test5() {
// 前置增強(qiáng)
// 1热芹、實(shí)例化bean和增強(qiáng)
Animal dog = new Dog();
MyMethodBeforeAdvice advice = new MyMethodBeforeAdvice();
// 2、創(chuàng)建ProxyFactory并設(shè)置代理目標(biāo)和增強(qiáng)
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(dog);
proxyFactory.addAdvice(advice);
// 3惨撇、生成代理實(shí)例
Animal proxyDog = (Animal) proxyFactory.getProxy();
proxyDog.sayHello("二哈", 3);
System.out.println("\n\n");
proxyDog.sayException("二哈", 3);
}
==前置增強(qiáng)
==方法名:sayHello
==第1參數(shù):二哈
==第2參數(shù):3
==目標(biāo)類信息:com.lyc.cn.v2.day04.advisor.Dog@65e579dc
==名字:二哈 年齡:3
==前置增強(qiáng)
==方法名:sayException
==第1參數(shù):二哈
==第2參數(shù):3
==目標(biāo)類信息:com.lyc.cn.v2.day04.advisor.Dog@65e579dc
==名字:二哈 年齡:3
java.lang.ArithmeticException: / by zero
at com.lyc.cn.v2.day04.advisor.Dog.sayException(Dog.java:17)
at com.lyc.cn.v2.day04.advisor.Dog$$FastClassBySpringCGLIB$$a974b1ec.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
從測(cè)試結(jié)果上看,只要我們調(diào)用了Animal類的接口府寒,增強(qiáng)方法都會(huì)被應(yīng)用到目標(biāo)類的方法上魁衙,這樣的增強(qiáng)效果肯定不能滿足我們實(shí)際的應(yīng)用,那么這個(gè)時(shí)候就需要引入一個(gè)概念----切入點(diǎn)(Pointcut)
株搔。通過切入點(diǎn)就可以有選擇的將增強(qiáng)應(yīng)用到目標(biāo)類的方法上剖淀,而目標(biāo)類的方法就是我們上一節(jié)說的連接點(diǎn),即sayHello和sayException方法纤房,目標(biāo)類就是要被增強(qiáng)的類纵隔,即Dog類,所以增強(qiáng)描述了連接點(diǎn)的方位信息,例如織入到方法之前捌刮、方法之后碰煌,而切入點(diǎn)則進(jìn)一步的描述了織入到那些類的那些方法上。到這里相信大家對(duì)連接點(diǎn)绅作、切入點(diǎn)芦圾、增強(qiáng)、目標(biāo)對(duì)象等概念有了更為深刻的理解俄认。
但是這又帶了一個(gè)新的問題,那就是如何將切入點(diǎn)定位到連接點(diǎn),換言之狂塘,就是切入點(diǎn)如何知道自己要被應(yīng)用到那些連接點(diǎn)上呢懒震?
接下來就有必要看一下Pointcut接口的源碼了。
- Pointcut接口
public interface Pointcut {
/**
* 返回當(dāng)前切點(diǎn)匹配的類
*/
ClassFilter getClassFilter();
/**
* 返回當(dāng)前切點(diǎn)匹配的方法
*/
MethodMatcher getMethodMatcher();
/**
* Canonical Pointcut instance that always matches.
*/
Pointcut TRUE = TruePointcut.INSTANCE;
}
Pointcut接口的定義非常簡(jiǎn)單岂贩,僅僅包含了ClassFilter和MethodMatcher的定義糊探,ClassFilter可以定位到具體的類上,MethodMatcher可以定位到具體的方法上河闰,這樣通過Pointcut我們就可以將將增強(qiáng)織入到特定類的特定方法上了科平。再來看下ClassFilter和MethodMatcher的定義:
- ClassFilter接口
public interface ClassFilter {
/**
* 切入點(diǎn)應(yīng)該應(yīng)用于給定的接口還是目標(biāo)類
* Should the pointcut apply to the given interface or target class?
* @param clazz the candidate target class 候選目標(biāo)類
* @return whether the advice should apply to the given target class 增強(qiáng)是否應(yīng)用于目標(biāo)類
*/
boolean matches(Class<?> clazz);
/**
* Canonical instance of a ClassFilter that matches all classes.
*/
ClassFilter TRUE = TrueClassFilter.INSTANCE;
}
- MethodMatcher接口
public interface MethodMatcher {
/**
* 靜態(tài)方法匹配判斷
*/
boolean matches(Method method, Class<?> targetClass);
/**
* 判斷靜態(tài)方法匹配或動(dòng)態(tài)方法匹配
* true:動(dòng)態(tài)方法匹配
* false:靜態(tài)方法匹配
*/
boolean isRuntime();
/**
* 動(dòng)態(tài)方法匹配判斷
*/
boolean matches(Method method, Class<?> targetClass, Object... args);
/**
* Canonical instance that matches all methods.
*/
MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
}
雖然還沒有看到以上三個(gè)接口的具體實(shí)現(xiàn),但是現(xiàn)在我們只要知道Pointcut接口提供了這樣的功能就行了姜性。在MethodMatcher接口中又引入了一個(gè)新的概念瞪慧,方法匹配模式,Spring支持兩種方法匹配器:
- 靜態(tài)方法匹模式:所謂靜態(tài)方法匹配器部念,僅對(duì)方法名簽名(包括方法名和入?yún)㈩愋图绊樞颍┻M(jìn)行匹配弃酌。
- 動(dòng)態(tài)方法匹配器:動(dòng)態(tài)方法匹配器會(huì)在運(yùn)行期方法檢查入?yún)⒌闹怠?靜態(tài)匹配僅會(huì)判斷一次,而動(dòng)態(tài)匹配因?yàn)槊看握{(diào)用方法的入?yún)⒖赡懿灰粯永芰叮悦看握{(diào)用方法都必須判斷妓湘。
接下來簡(jiǎn)單介紹一下Spring提供的切點(diǎn)類型:
- 靜態(tài)方法切點(diǎn)-->org.springframework.aop.support.StaticMethodMatcherPointcut
靜態(tài)方法切點(diǎn)的抽象基類,默認(rèn)情況下匹配所有的類乌询。最常用的兩個(gè)子類NameMatchMethodPointcut和 AbstractRegexpMethodPointcut 榜贴, 前者提供簡(jiǎn)單字符串匹配方法簽名,后者使用正則表達(dá)式匹配方法簽名妹田。 - 動(dòng)態(tài)方法切點(diǎn)-->org.springframework.aop.support.DynamicMethodMatcherPointcut
動(dòng)態(tài)方法切點(diǎn)的抽象基類唬党,默認(rèn)情況下匹配所有的類 - 注解切點(diǎn)-->org.springframework.aop.support.annotation.AnnotationMatchingPointcut
- 表達(dá)式切點(diǎn)-->org.springframework.aop.support.ExpressionPointcut
提供了對(duì)AspectJ切點(diǎn)表達(dá)式語法的支持 - 流程切點(diǎn)-->org.springframework.aop.support.ControlFlowPointcut
該切點(diǎn)是一個(gè)比較特殊的節(jié)點(diǎn),它根據(jù)程序執(zhí)行的堆棧信息查看目標(biāo)方法是否由某一個(gè)方法直接或間接發(fā)起調(diào)用鬼佣,一次來判斷是否為匹配的鏈接點(diǎn) - 復(fù)合切點(diǎn)-->org.springframework.aop.support.ComposablePointcut
該類是為實(shí)現(xiàn)創(chuàng)建多個(gè)切點(diǎn)而提供的操作類
2.切面簡(jiǎn)介
由于增強(qiáng)包括橫切代碼驶拱,又包含部分連接點(diǎn)信息(方法前、方法后主方位信息)晶衷,所以可以僅通過增強(qiáng)類生成一個(gè)切面蓝纲。 但切點(diǎn)僅僅代表目標(biāo)類連接點(diǎn)的部分信息(類和方法的定位)阴孟,所以僅有切點(diǎn)無法制作出一個(gè)切面,必須結(jié)合增強(qiáng)才能制作出切面税迷。Spring使用org.springframework.aop.Advisor接口標(biāo)識(shí)切面概念永丝,一個(gè)切面同時(shí)包含橫切代碼和連接點(diǎn)信息。
切面可以分為3類:一般切面翁狐、切點(diǎn)切面类溢、引介切面
一般切面Advisor
org.springframework.aop.Advisor代表一般切面,僅包含一個(gè)Advice ,因?yàn)锳dvice包含了橫切代碼和連接點(diǎn)信息露懒,所以Advice本身一個(gè)簡(jiǎn)單的切面闯冷,只不過它代表的橫切的連接點(diǎn)是所有目標(biāo)類的所有方法,因?yàn)檫@個(gè)橫切面太寬泛懈词,所以一般不會(huì)直接使用蛇耀。-
切點(diǎn)切面PointcutAdvisor
org.springframework.aop.PointcutAdvisor ,代表具有切點(diǎn)的切面,包括Advice和Pointcut兩個(gè)類坎弯,這樣就可以通過類纺涤、方法名以及方位等信息靈活的定義切面的連接點(diǎn),提供更具實(shí)用性的切面抠忘。PointcutAdvisor主要有6個(gè)具體的實(shí)現(xiàn)類:- DefaultPointcutAdvisor:最常用的切面類型撩炊,它可以通過任意Pointcut和Advice定義一個(gè)切面,唯一不支持的就是引介的切面類型崎脉,一般可以通過擴(kuò)展該類實(shí)現(xiàn)自定義的切面
- NameMatchMethodPointcutAdvisor:通過該類可以定義按方法名定義切點(diǎn)的切面
- AspectJExpressionPointcutAdvisor:用于AspectJ切點(diǎn)表達(dá)式定義切點(diǎn)的切面
- StaticMethodMatcherPointcutAdvisor:靜態(tài)方法匹配器切點(diǎn)定義的切面拧咳,默認(rèn)情況下匹配所有的的目標(biāo)類
- AspectJPointcutAdvisor:用于AspectJ語法定義切點(diǎn)的切面
- 引介切面IntroductionAdvisor
org.springframework.aop.IntroductionAdvisor代表引介切面, 引介切面是對(duì)應(yīng)引介增強(qiáng)的特殊的切面囚灼,它應(yīng)用于類層上面骆膝,所以引介切點(diǎn)使用ClassFilter進(jìn)行定義。
3.靜態(tài)普通方法名匹配切面
上面已經(jīng)對(duì)切入點(diǎn)灶体、切面做了簡(jiǎn)介阅签,下面通過幾個(gè)例子來加深大家的印象。先來看靜態(tài)普通方法名匹配切面蝎抽,前面我們介紹切入點(diǎn) 通過ClassFilter可以定位到具體的類上政钟,MethodMatcher可以定位到具體的方法上
,那么接下來通過定義一個(gè)接口、兩個(gè)類织中。并通過實(shí)現(xiàn)類中的不同方法來驗(yàn)證我們之前的介紹锥涕。
- 接口和實(shí)現(xiàn)類(目標(biāo)對(duì)象)
package com.lyc.cn.v2.day05;
/**
* @author: LiYanChao
* @create: 2018-11-04 22:40
*/
public interface Animal {
void sayHello();
}
package com.lyc.cn.v2.day05;
/**
* @author: LiYanChao
* @create: 2018-11-04 22:09
*/
public class Cat implements Animal {
@Override
public void sayHello() {
System.out.println("我是Cat類的sayHello方法。狭吼。。");
}
public void sayHelloCat() {
System.out.println("我是一只貓殖妇。刁笙。。");
}
}
package com.lyc.cn.v2.day05;
/**
* @author: LiYanChao
* @create: 2018-11-04 22:09
*/
public class Dog implements Animal{
@Override
public void sayHello() {
System.out.println("我是Dog類的sayHello方法。疲吸。座每。");
}
public void sayHelloDog() {
System.out.println("我是一只狗。摘悴。峭梳。");
}
}
- 增強(qiáng)(為了演示,這里只實(shí)現(xiàn)MethodBeforeAdvice前置增強(qiáng)接口)
package com.lyc.cn.v2.day05;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
/**
* 前置增強(qiáng)
* @author: LiYanChao
* @create: 2018-11-01 21:50
*/
public class MyMethodBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("==前置增強(qiáng)");
System.out.println("==方法名:" + method.getName());
if (null != args && args.length > 0) {
for (int i = 0; i < args.length; i++) {
System.out.println("==第" + (i + 1) + "參數(shù):" + args[i]);
}
}
System.out.println("==目標(biāo)類信息:" + target.toString());
}
}
- 切面
package com.lyc.cn.v2.day05;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
import java.lang.reflect.Method;
/**
* 靜態(tài)普通方法名匹配切面
* @author: LiYanChao
* @create: 2018-11-04 22:08
*/
public class MyStaticPointcutAdvisor extends StaticMethodMatcherPointcutAdvisor {
private static String METHOD_NAME = "sayHello";
/**
* 靜態(tài)方法匹配判斷蹂喻,這里只有方法名為sayHello的葱椭,才能被匹配
*/
@Override
public boolean matches(Method method, Class<?> targetClass) {
return METHOD_NAME.equals(method.getName());
}
/**
* 覆蓋getClassFilter,只匹配Dog類
* @return
*/
public ClassFilter getClassFilter() {
return new ClassFilter() {
@Override
public boolean matches(Class<?> clazz) {
return Dog.class.isAssignableFrom(clazz);
}
};
}
}
StaticMethodMatcherPointcutAdvisor抽象類繼承了StaticMethodMatcherPointcut類并實(shí)現(xiàn)了PointcutAdvisor接口口四。在MyStaticPointcutAdvisor類中我們實(shí)現(xiàn)了matches靜態(tài)方法匹配判斷孵运,并且只有方法名為sayHello的,才能被匹配蔓彩;覆蓋了getClassFilter方法治笨,并且只匹配Dog類。
- 測(cè)試一
為了大家能夠更便捷的使用測(cè)試類赤嚼,也為了減少大家書寫配置文件的負(fù)擔(dān)旷赖,我們還是采用編碼的形式實(shí)現(xiàn)。新建MyTest類并書寫測(cè)試方法更卒。
@Test
public void test1() {
// 1等孵、創(chuàng)建目標(biāo)類、增強(qiáng)逞壁、切入點(diǎn)
Animal animal = new Dog();
MyMethodBeforeAdvice advice = new MyMethodBeforeAdvice();
MyStaticPointcutAdvisor advisor = new MyStaticPointcutAdvisor();
// 2流济、創(chuàng)建ProxyFactory并設(shè)置目標(biāo)類、增強(qiáng)腌闯、切面
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(animal);
// 為切面類提供增強(qiáng)
advisor.setAdvice(advice);
proxyFactory.addAdvisor(advisor);
// 3绳瘟、生成代理實(shí)例
Dog proxyDog = (Dog) proxyFactory.getProxy();
proxyDog.sayHelloDog();
System.out.println("\n\n");
proxyDog.sayHello();
}
我是一只狗。姿骏。糖声。
==前置增強(qiáng)
==方法名:sayHello
==目標(biāo)類信息:com.lyc.cn.v2.day05.Dog@65e579dc
我是Dog類的sayHello方法。分瘦。蘸泻。
之前我們?cè)诖a里配置了,在類一級(jí)只匹配Dog類嘲玫,在方法一級(jí)只匹配sayHello方法悦施。運(yùn)行結(jié)果與我們的設(shè)置符合。只有Dog類的sayHello被增強(qiáng)了去团。
- 測(cè)試二
@Test
public void test2() {
// 1抡诞、創(chuàng)建目標(biāo)類穷蛹、增強(qiáng)、切入點(diǎn)
Animal animal = new Cat();
MyMethodBeforeAdvice advice = new MyMethodBeforeAdvice();
MyStaticPointcutAdvisor advisor = new MyStaticPointcutAdvisor();
// 2昼汗、創(chuàng)建ProxyFactory并設(shè)置目標(biāo)類肴熏、增強(qiáng)、切面
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(animal);
// 為切面類提供增強(qiáng)
advisor.setAdvice(advice);
proxyFactory.addAdvisor(advisor);
// 3顷窒、生成代理實(shí)例
Cat proxyDog = (Cat) proxyFactory.getProxy();
proxyDog.sayHelloCat();
System.out.println("\n\n");
proxyDog.sayHello();
}
我是一只貓蛙吏。。鞋吉。
我是Cat類的sayHello方法鸦做。。坯辩。
測(cè)試二的結(jié)果沒有一個(gè)方法被增強(qiáng)馁龟,雖然在Cat類中也有sayHello方法,但是我們?cè)O(shè)置的是只匹配Dog類漆魔,所以雖然在Cat類中有sayHello方法坷檩,但是它也是無法被增強(qiáng)的。
至于其他的切點(diǎn)和切面改抡,這里就不一一演示了矢炼,這里特別感謝《Spring3.X企業(yè)應(yīng)用開發(fā)實(shí)戰(zhàn)》這本書,本篇很多介紹均摘自本書阿纤,哈哈句灌!
4.總結(jié)
本篇主要介紹了切點(diǎn)和切面的概念,并通過實(shí)際的例子為大家演示了切點(diǎn)是如何匹配類和方法的欠拾。概念性的東西大家只看簡(jiǎn)介是不行的胰锌,需要自己動(dòng)手寫代碼,才能更深刻的理解AOP的相關(guān)概念藐窄,在接下來的源碼分析中才不會(huì)陷入迷茫资昧。
上一篇和本篇的測(cè)試類中,我們都是通過ProxyFactory創(chuàng)建的代理荆忍,這樣的實(shí)現(xiàn)肯定無法滿足我們的實(shí)際需要格带,那么接下來的篇幅,我們就要介紹Spring的自動(dòng)代理機(jī)制刹枉。