面向對象編程,也稱爲OOP(即Object Oriented Programming)最大的優勢在於可以將業務模塊進行封裝,從而達到功能複用的目的。經過面向對象編程,不一樣的模板能夠相互組裝,從而實現更爲複雜的業務模塊,其結構形式可用下圖表示:java
面向對象編程解決了業務模塊的封裝複用的問題,可是對於某些模塊,其自己並不獨屬於摸個業務模塊,而是根據不一樣的狀況,貫穿於某幾個或所有的模塊之間的。例如登陸驗證,其只開放幾個能夠不用登陸的接口給用戶使用(通常登陸使用攔截器實現,可是其切面思想是一致的);再好比性能統計,其須要記錄每一個業務模塊的調用,而且監控器調用時間。能夠看到,這些橫貫於每一個業務模塊的模塊,若是使用面向對象的方式,那麼就須要在已封裝的每一個模塊中添加相應的重複代碼,對於這種狀況,面向切面編程就能夠派上用場了。spring
面向切面編程,也稱爲AOP(即Aspect Oriented Programming),指的是將必定的切面邏輯按照必定的方式編織到指定的業務模塊中,從而將這些業務模塊的調用包裹起來。以下是其結構示意圖:express
這裏須要說明的是,@Before是業務邏輯執行前執行,與其對應的是@AfterReturning,而不是@After,@After是全部的切面邏輯執行完以後纔會執行,不管是否拋出異常。編程
因爲Spring切面粒度最小是達到方法級別,而execution表達式能夠用於明確指定方法返回類型,類名,方法名和參數名等與方法相關的部件,而且在Spring中,大部分須要使用AOP的業務場景也只須要達到方法級別便可,於是execution表達式的使用是最爲普遍的。以下是execution表達式的語法:多線程
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
這裏問號表示當前項能夠有也能夠沒有,其中各項的語義以下:app
以下是一個使用execution表達式的例子:ide
execution(public * com.spring.service.BusinessObject.businessService(java.lang.String,..))
上述切點表達式將會匹配使用public修飾,返回值爲任意類型,而且是com.spring.BusinessObject類中名稱爲businessService的方法,方法能夠有多個參數,可是第一個參數必須是java.lang.String類型的方法。上述示例中咱們使用了..通配符,關於通配符的類型,主要有兩種:性能
以下示例表示返回值爲任意類型,在com.spring.service.BusinessObject類中,而且參數個數爲零的方法:ui
execution(* com.spring.service.BusinessObject.*())
下述示例表示返回值爲任意類型,在com.spring.service包中,以Business爲前綴的類,而且是類中參數個數爲零方法:this
execution(* com.spring.service.Business*.*())
以下示例表示匹配返回值爲任意類型,而且是com.spring.service包及其子包下的任意類的名稱爲businessService的方法,並且該方法不能有任何參數:
execution(* com.spring.service..*.businessService())
這裏須要說明的是,包路徑service..*.businessService()中的..應該理解爲延續前面的service路徑,表示到service路徑爲止,或者繼續延續service路徑,從而包括其子包路徑;後面的*.businessService(),這裏的*表示匹配一個單詞,由於是在方法名前,於是表示匹配任意的類。
以下示例是使用..表示任意個數的參數的示例,須要注意,表示參數的時候能夠在括號中事先指定某些類型的參數,而其他的參數則由..進行匹配:
execution(* com.spring.service.BusinessObject.businessService(java.lang.String,..))
within表達式的粒度爲類,其參數爲全路徑的類名(可以使用通配符),表示匹配當前表達式的全部類都將被當前方法環繞。以下是within表達式的語法:
within(declaring-type-pattern)
within表達式只能指定到類級別,以下示例表示匹配com.spring.service.BusinessObject中的全部方法:
within(com.spring.service.BusinessObject)
within表達式路徑和類名均可以使用通配符進行匹配,好比以下表達式將匹配com.spring.service包下的全部類,不包括子包中的類:
within(com.spring.service.*)
以下表達式表示匹配com.spring.service包及子包下的全部類:
within(com.spring.service..*)
args表達式的做用是匹配指定參數類型和指定參數數量的方法,不管其類路徑或者是方法名是什麼。這裏須要注意的是,args指定的參數必須是全路徑的。以下是args表達式的語法:
args(param-pattern)
以下示例表示匹配全部只有一個參數,而且參數類型是java.lang.String類型的方法:
args(java.lang.String)
也可使用通配符,但這裏通配符只能使用..,而不能使用*。以下是使用通配符的實例,該切點表達式將匹配第一個參數爲java.lang.String,最後一個參數爲java.lang.Integer,而且中間能夠有任意個數和類型參數的方法:
args(java.lang.String,..,java.lang.Integer)
this和target須要放在一塊兒進行講解,主要目的是對其進行區別。this和target表達式中都只能指定類或者接口,在面向切面編程規範中,this表示匹配調用當前切點表達式所指代對象方法的對象,target表示匹配切點表達式指定類型的對象。好比有兩個類A和B,而且A調用了B的某個方法,若是切點表達式爲this(B),那麼A的實例將會被匹配,也即其會被使用當前切點表達式的Advice環繞;若是這裏切點表達式爲target(B),那麼B的實例也即被匹配,其將會被使用當前切點表達式的Advice環繞。
在講解Spring中的this和target的使用以前,首先須要講解一個概念:業務對象(目標對象)和代理對象。對於切面編程,有一個目標對象,也有一個代理對象,目標對象是咱們聲明的業務邏輯對象,而代理對象是使用切面邏輯對業務邏輯進行包裹以後生成的對象。若是使用的是Jdk動態代理,那麼業務對象和代理對象將是兩個對象,在調用代理對象邏輯時,其切面邏輯中會調用目標對象的邏輯;若是使用的是Cglib代理,因爲是使用的子類進行切面邏輯織入的,那麼只有一個對象,即織入了代理邏輯的業務類的子類對象,此時是不會生成業務類的對象的。
在Spring中,其對this的語義進行了改寫,即若是當前對象生成的代理對象符合this指定的類型,那麼就爲其織入切面邏輯。簡單的說就是,this將匹配代理對象爲指定類型的類。target的語義則沒有發生變化,即其將匹配業務對象爲指定類型的類。以下是使用this和target表達式的簡單示例:
this(com.spring.service.BusinessObject)
target(com.spring.service.BusinessObject)
經過上面的講解能夠看出,this和target的使用區別其實不大,大部分狀況下其使用效果是同樣的,但其區別也仍是有的。Spring使用的代理方式主要有兩種:Jdk代理和Cglib代理(關於這兩種代理方式的講解能夠查看本人的文章代理模式實現方式及優缺點對比)。針對這兩種代理類型,關於目標對象與代理對象,理解以下兩點是很是重要的:
結合上述兩點說明,這裏理解this和target的異同就相對比較簡單了。咱們這裏分三種狀況進行說明:
關於this和target的異同,咱們使用以下示例進行簡單演示:
// 目標類 public class Apple { public void eat() { System.out.println("Apple.eat method invoked."); } }
// 切面類 @Aspect public class MyAspect { @Around("this(com.business.Apple)") public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("this is before around advice"); Object result = pjp.proceed(); System.out.println("this is after around advice"); return result; } }
<!-- bean聲明文件 --> <bean id="apple" class="chapter7.eg1.Apple"/> <bean id="aspect" class="chapter7.eg6.MyAspect"/> <aop:aspectj-autoproxy/>
// 驅動類 public class AspectApp { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Apple fruit = (Apple) context.getBean("apple"); fruit.eat(); } }
執行驅動類中的main方法,結果以下:
this is before around advice Apple.eat method invoked. this is after around advice
上述示例中,Apple沒有實現任何接口,於是使用的是Cglib代理,this表達式會匹配Apple對象。這裏將切點表達式更改成target,仍是執行上述代碼,會發現結果仍是同樣的:
target(com.business.Apple)
若是咱們對Apple的聲明進行修改,使其實現一個接口,那麼這裏就會顯示出this和target的執行區別了:
public class Apple implements IApple { public void eat() { System.out.println("Apple.eat method invoked."); } }
public class AspectApp { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Fruit fruit = (Fruit) context.getBean("apple"); fruit.eat(); } }
咱們仍是執行上述代碼,對於this表達式,其執行結果以下:
Apple.eat method invoked.
對於target表達式,其執行結果以下:
this is before around advice Apple.eat method invoked. this is after around advice
能夠看到,這種狀況下this和target表達式的執行結果是不同的,這正好符合咱們前面講解的第三種狀況。
前面咱們講解了within的語義表示匹配指定類型的類實例,這裏的@within表示匹配帶有指定註解的類,其使用語法以下所示:
@within(annotation-type)
以下所示示例表示匹配使用com.spring.annotation.BusinessAspect註解標註的類:
@within(com.spring.annotation.BusinessAspect)
這裏咱們使用一個例子演示@within的用法(這裏驅動類和xml文件配置與3.4節使用的一致,這裏省略):
// 註解類 @Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface FruitAspect { }
// 目標類 @FruitAspect public class Apple { public void eat() { System.out.println("Apple.eat method invoked."); } }
// 切面類 @Aspect public class MyAspect { @Around("@within(com.business.annotation.FruitAspect)") public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("this is before around advice"); Object result = pjp.proceed(); System.out.println("this is after around advice"); return result; } }
上述切面表示匹配使用FruitAspect註解的類,而Apple則使用了該註解,於是Apple類方法的調用會被切面環繞,執行運行驅動類可獲得以下結果,說明Apple.eat()方法確實被環繞了:
this is before around advice Apple.eat method invoked. this is after around advice
@annotation的使用方式與@within的類似,表示匹配使用@annotation指定註解標註的方法將會被環繞,其使用語法以下:
@annotation(annotation-type)
以下示例表示匹配使用com.spring.annotation.BusinessAspect註解標註的方法:
@annotation(com.spring.annotation.BusinessAspect)
這裏咱們繼續複用3.5節使用的例子進行講解@annotation的用法,只是這裏須要對Apple和MyAspect使用和指定註解的方式進行修改,FruitAspect不用修改的緣由是聲明該註解時已經指定了其可使用在類,方法和參數上:
// 目標類,將FruitAspect移到了方法上 public class Apple { @FruitAspect public void eat() { System.out.println("Apple.eat method invoked."); } }
@Aspect public class MyAspect { @Around("@annotation(com.business.annotation.FruitAspect)") public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("this is before around advice"); Object result = pjp.proceed(); System.out.println("this is after around advice"); return result; } }
這裏Apple.eat()方法使用FruitAspect註解進行了標註,於是該方法的執行會被切面環繞,其執行結果以下:
this is before around advice Apple.eat method invoked. this is after around advice
@within和@annotation分別表示匹配使用指定註解標註的類和標註的方法將會被匹配,@args則表示使用指定註解標註的類做爲某個方法的參數時該方法將會被匹配。以下是@args註解的語法:
@args(annotation-type)
以下示例表示匹配使用了com.spring.annotation.FruitAspect註解標註的類做爲參數的方法:
@args(com.spring.annotation.FruitAspect)
這裏咱們使用以下示例對@args的用法進行講解:
<!-- xml配置文件 --> <bean id="bucket" class="chapter7.eg1.FruitBucket"/> <bean id="aspect" class="chapter7.eg6.MyAspect"/> <aop:aspectj-autoproxy/>
// 使用註解標註的參數類 @FruitAspect public class Apple {}
// 使用Apple參數的目標類 public class FruitBucket { public void putIntoBucket(Apple apple) { System.out.println("put apple into bucket."); } }
@Aspect public class MyAspect { @Around("@args(chapter7.eg6.FruitAspect)") public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("this is before around advice"); Object result = pjp.proceed(); System.out.println("this is after around advice"); return result; } }
// 驅動類 public class AspectApp { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); FruitBucket bucket = (FruitBucket) context.getBean("bucket"); bucket.putIntoBucket(new Apple()); } }
這裏FruitBucket.putIntoBucket(Apple)方法的參數Apple使用了@args註解指定的FruitAspect進行了標註,於是該方法的調用將會被環繞。執行驅動類,結果以下:
this is before around advice put apple into bucket. this is after around advice
@DeclareParents也稱爲Introduction(引入),表示爲指定的目標類引入新的屬性和方法。關於@DeclareParents的原理其實比較好理解,由於不管是Jdk代理仍是Cglib代理,想要引入新的方法,只須要經過必定的方式將新聲明的方法織入到代理類中便可,由於代理類都是新生成的類,於是織入過程也比較方便。以下是@DeclareParents的使用語法:
@DeclareParents(value = "TargetType", defaultImpl = WeaverType.class) private WeaverInterface attribute;
這裏TargetType表示要織入的目標類型(帶全路徑),WeaverInterface中聲明瞭要添加的方法,WeaverType中聲明瞭要織入的方法的具體實現。以下示例表示在Apple類中織入IDescriber接口聲明的方法:
@DeclareParents(value = "com.spring.service.Apple", defaultImpl = DescriberImpl.class) private IDescriber describer;
這裏咱們使用一個以下實例對@DeclareParents的使用方式進行講解,配置文件與3.4節的一致,這裏略:
// 織入方法的目標類 public class Apple { public void eat() { System.out.println("Apple.eat method invoked."); } }
// 要織入的接口 public interface IDescriber { void desc(); }
// 要織入接口的默認實現 public class DescriberImpl implements IDescriber { @Override public void desc() { System.out.println("this is an introduction describer."); } }
// 切面實例 @Aspect public class MyAspect { @DeclareParents(value = "com.spring.service.Apple", defaultImpl = DescriberImpl.class) private IDescriber describer; }
// 驅動類 public class AspectApp { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); IDescriber describer = (IDescriber) context.getBean("apple"); describer.desc(); } }
在MyAspect中聲明瞭咱們須要將IDescriber的方法織入到Apple實例中,在驅動類中咱們能夠看到,咱們獲取的是apple實例,可是獲得的bean卻能夠強轉爲IDescriber類型,於是說明咱們的織入操做成功了。
在Spring AOP中,切面類的實例只有一個,好比前面咱們一直使用的MyAspect類,假設咱們使用的切面類須要具備某種狀態,以適用某些特殊狀況的使用,好比多線程環境,此時單例的切面類就不符合咱們的要求了。在Spring AOP中,切面類默認都是單例的,但其還支持另外兩種多例的切面實例的切面,即perthis和pertarget,須要注意的是perthis和pertarget都是使用在切面類的@Aspect註解中的。這裏perthis和pertarget表達式中都是指定一個切面表達式,其語義與前面講解的this和target很是的類似,perthis表示若是某個類的代理類符合其指定的切面表達式,那麼就會爲每一個符合條件的目標類都聲明一個切面實例;pertarget表示若是某個目標類符合其指定的切面表達式,那麼就會爲每一個符合條件的類聲明一個切面實例。從上面的語義能夠看出,perthis和pertarget的含義是很是類似的。以下是perthis和pertarget的使用語法:
perthis(pointcut-expression)
pertarget(pointcut-expression)
因爲perthis和pertarget的使用效果大部分狀況下都是一致的,咱們這裏主要講解perthis和pertarget的區別。關於perthis和pertarget的使用,須要注意的一個點是,因爲perthis和pertarget都是爲每一個符合條件的類聲明一個切面實例,於是切面類在配置文件中的聲明上必定要加上prototype,不然Spring啓動是會報錯的。以下是咱們使用的示例:
<!-- xml配置文件 --> <bean id="apple" class="chapter7.eg1.Apple"/> <bean id="aspect" class="chapter7.eg6.MyAspect" scope="prototype"/> <aop:aspectj-autoproxy/>
// 目標類實現的接口 public interface Fruit { void eat(); }
// 業務類 public class Apple implements Fruit { public void eat() { System.out.println("Apple.eat method invoked."); } }
// 切面類 @Aspect("perthis(this(com.spring.service.Apple))") public class MyAspect { public MyAspect() { System.out.println("create MyAspect instance, address: " + toString()); } @Around("this(com.spring.service.Apple)") public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("this is before around advice"); Object result = pjp.proceed(); System.out.println("this is after around advice"); return result; } }
// 驅動類 public class AspectApp { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Fruit fruit = context.getBean(Fruit.class); fruit.eat(); } }
這裏咱們使用的切面表達式語法爲perthis(this(com.spring.service.Apple)),這裏this表示匹配代理類是Apple類型的類,perthis則表示會爲這些類的每一個實例都建立一個切面類。因爲Apple實現了Fruit接口,於是Spring使用Jdk動態代理爲其生成代理類,也就是說代理類與Apple都實現了Fruit接口,可是代理類不是Apple類型,於是這裏聲明的切面不會匹配到Apple類。執行上述驅動類,結果以下:
Apple.eat method invoked.
結果代表Apple類確實沒有被環繞。若是咱們講切面類中的perthis和this修改成pertarget和target,效果如何呢:
@Aspect("pertarget(target(com.spring.service.Apple))") public class MyAspect { public MyAspect() { System.out.println("create MyAspect instance, address: " + toString()); } @Around("target(com.spring.service.Apple)") public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("this is before around advice"); Object result = pjp.proceed(); System.out.println("this is after around advice"); return result; } }
執行結果以下:
create MyAspect instance, address: chapter7.eg6.MyAspect@48fa0f47 this is before around advice Apple.eat method invoked. this is after around advice
能夠看到,Apple類被切面環繞了。這裏target表示目標類是Apple類型,雖然Spring使用了Jdk動態代理實現切面的環繞,代理類雖不是Apple類型,可是目標類倒是Apple類型,符合target的語義,而pertarget會爲每一個符合條件的表達式的類實例建立一個代理類實例,於是這裏Apple會被環繞。
因爲代理類與目標類的差異很是小,於是與this和target同樣,perthis和pertarget的區別也很是小,大部分狀況下其使用效果是一致的。關於切面多實例的建立,其演示比較簡單,咱們能夠將xml文件中的Apple實例修改成prototype類型,而且在驅動類中屢次獲取Apple類的實例:
<!-- xml配置文件 --> <bean id="apple" class="chapter7.eg1.Apple" scope="prototype"/> <bean id="aspect" class="chapter7.eg6.MyAspect" scope="prototype"/> <aop:aspectj-autoproxy/>
public class AspectApp { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Fruit fruit = context.getBean(Fruit.class); fruit.eat(); fruit = context.getBean(Fruit.class); fruit.eat(); } }
執行結果以下:
create MyAspect instance, address: chapter7.eg6.MyAspect@48fa0f47 this is before around advice Apple.eat method invoked. this is after around advice create MyAspect instance, address: chapter7.eg6.MyAspect@56528192 this is before around advice Apple.eat method invoked. this is after around advice
執行結果中兩次打印的create MyAspect instance表示當前切面實例建立了兩次,這也符合咱們進行的兩次獲取Apple實例。
本文首先對AOP進行了簡單介紹,而後介紹了切面中的各個角色,最後詳細介紹了切點表達式中各個不一樣類型表達式的語法。