免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
[AOP] 2. AOP的兩種實(shí)現(xiàn)-Spring AOP以及AspectJ

在接觸Spring以及種類繁多的Java框架時(shí),很多開發(fā)人員(至少包括我)都會(huì)覺得注解是個(gè)很奇妙的存在,為什么加上了@Transactional之后,方法會(huì)在一個(gè)事務(wù)的上下文中被執(zhí)行呢?為什么加上了@Cacheable之后,方法的返回值會(huì)被記錄到緩存中,從而讓下次的重復(fù)調(diào)用能夠直接利用緩存的結(jié)果呢?

隨著對(duì)AOP的逐漸應(yīng)用和了解,才明白注解只是一個(gè)表象,在幕后Spring AOP/AspectJ做了大量的工作才得以實(shí)現(xiàn)這些神奇的功能。

那么,本文就來聊一聊Spring AOP和AspectJ的那些事,它們究竟有什么魔力才讓這一切成為現(xiàn)實(shí)。

Spring AOP

基于代理(Proxy)的AOP實(shí)現(xiàn)

首先,這是一種基于代理(Proxy)的實(shí)現(xiàn)方式。下面這張圖很好地表達(dá)了這層關(guān)系:

這張圖反映了參與到AOP過程中的幾個(gè)關(guān)鍵組件(以@Before Advice為例):

  1. 調(diào)用者Beans - 即調(diào)用發(fā)起者,它只知道目標(biāo)方法所在Bean,并不清楚代理以及Advice的存在
  2. 目標(biāo)方法所在Bean - 被調(diào)用的目標(biāo)方法
  3. 生成的代理 - 由Spring AOP為目標(biāo)方法所在Bean生成的一個(gè)代理對(duì)象
  4. Advice - 切面的執(zhí)行邏輯

它們之間的調(diào)用先后次序反映在上圖的序號(hào)中:

  1. 調(diào)用者Bean嘗試調(diào)用目標(biāo)方法,但是被生成的代理截了胡
  2. 代理根據(jù)Advice的種類(本例中是@Before Advice),對(duì)Advice首先進(jìn)行調(diào)用
  3. 代理調(diào)用目標(biāo)方法
  4. 返回調(diào)用結(jié)果給調(diào)用者Bean(由代理返回,沒有體現(xiàn)在圖中)

為了理解清楚這張圖的意思和代理在中間扮演的角色,不妨看看下面的代碼:

@Componentpublic class SampleBean {  public void advicedMethod() {  }  public void invokeAdvicedMethod() {    advicedMethod();  }}@Aspect@Componentpublic class SampleAspect {  @Before("execution(void advicedMethod())")  public void logException() {    System.out.println("Aspect被調(diào)用了");  }}sampleBean.invokeAdvicedMethod(); // 會(huì)打印出 "Aspect被調(diào)用了" 嗎?
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

SampleBean扮演的就是目標(biāo)方法所在Bean的角色,而SampleAspect扮演的則是Advice的角色。很顯然,被AOP修飾過的方法是advicedMethod(),而非invokeAdvicedMethod()。然而,invokeAdvicedMethod()方法在內(nèi)部調(diào)用了advicedMethod()。那么會(huì)打印出來Advice中的輸出嗎?

答案是不會(huì)。

如果想不通為什么會(huì)這樣,不妨再去仔細(xì)看看上面的示意圖。

這是在使用Spring AOP的時(shí)候可能會(huì)遇到的一個(gè)問題。類似這種間接調(diào)用不會(huì)觸發(fā)Advice的原因在于調(diào)用發(fā)生在目標(biāo)方法所在Bean的內(nèi)部,和外面的代理對(duì)象可是沒有半毛錢的關(guān)系哦。我們可以把這個(gè)代理想象成一個(gè)中介,只有它知道Advice的存在,調(diào)用者Bean和目標(biāo)方法所在Bean知道彼此的存在,但是對(duì)于代理或者是Advice卻是一無所知的。因此,沒有通過代理的調(diào)用是絕無可能觸發(fā)Advice的邏輯的。如下圖所示:

Spring AOP的兩種實(shí)現(xiàn)方式

Spring AOP有兩種實(shí)現(xiàn)方式:

  • 基于接口的動(dòng)態(tài)代理(Dynamic Proxy)
  • 基于子類化的CGLIB代理

我們?cè)谑褂肧pring AOP的時(shí)候,一般是不需要選擇具體的實(shí)現(xiàn)方式的。Spring AOP能根據(jù)上下文環(huán)境幫助我們選擇一種合適的。那么是不是每次都能夠這么”智能”地選擇出來呢?也不盡然,下面的例子就反映了這個(gè)問題:

@Componentpublic class SampleBean implements SampleInterface {  public void advicedMethod() {  }  public void invokeAdvicedMethod() {    advicedMethod();  }}public interface SampleInterface {}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

在上述代碼中,我們?yōu)樵瓉淼腂ean實(shí)現(xiàn)了一個(gè)新的接口SampleInterface,這個(gè)接口中并沒有定義任何方法。這個(gè)時(shí)候,再次運(yùn)行相關(guān)測(cè)試代碼的時(shí)候就會(huì)出現(xiàn)異常(摘錄了部分異常信息):

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.destiny1020.SampleBeanTest': Injection of autowired dependencies failedCaused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.destiny1020.SampleBean] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

也就是說在Test類中對(duì)于Bean的Autowiring失敗了,原因是創(chuàng)建SampleBeanTest Bean的時(shí)候發(fā)生了異常。那么為什么會(huì)出現(xiàn)創(chuàng)建Bean的異常呢?從異常信息來看并不明顯,實(shí)際上這個(gè)問題的根源在于Spring AOP在創(chuàng)建代理的時(shí)候出現(xiàn)了問題。

這個(gè)問題的根源可以在這里得到一些線索:

Spring AOP Reference - AOP Proxies

文檔中是這樣描述的(每段后加上了翻譯):

Spring AOP defaults to using standard JDK dynamic proxies for AOP proxies. This enables any interface (or set of interfaces) to be proxied.

Spring AOP默認(rèn)使用標(biāo)準(zhǔn)的JDK動(dòng)態(tài)代理來實(shí)現(xiàn)AOP代理。這能使任何借口(或者一組接口)被代理。

Spring AOP can also use CGLIB proxies. This is necessary to proxy classes rather than interfaces. CGLIB is used by default if a business object does not implement an interface. As it is good practice to program to interfaces rather than classes; business classes normally will implement one or more business interfaces. It is possible to force the use of CGLIB, in those (hopefully rare) cases where you need to advise a method that is not declared on an interface, or where you need to pass a proxied object to a method as a concrete type.

Spring AOP也使用CGLIB代理。對(duì)于代理classes而非接口這是必要的。如果一個(gè)業(yè)務(wù)對(duì)象沒有實(shí)現(xiàn)任何接口,那么默認(rèn)會(huì)使用CGLIB。由于面向接口而非面向classes編程是一個(gè)良好的實(shí)踐;業(yè)務(wù)對(duì)象通常都會(huì)實(shí)現(xiàn)一個(gè)或者多個(gè)業(yè)務(wù)接口。強(qiáng)制使用CGLIB也是可能的(希望這種情況很少),此時(shí)你需要advise的方法沒有被定義在接口中,或者你需要向方法中傳入一個(gè)具體的對(duì)象作為代理對(duì)象。

因此,上面異常的原因在于:

強(qiáng)制使用CGLIB也是可能的(希望這種情況很少),此時(shí)你需要advise的方法沒有被定義在接口中。

我們需要advise的方法是SampleBean中的advicedMethod方法。而在添加接口后,這個(gè)方法并沒有被定義在該接口中。所以正如文檔所言,我們需要強(qiáng)制使用CGLIB來避免這個(gè)問題。

強(qiáng)制使用CGLIB很簡單:

@Configuration@EnableAspectJAutoProxy(proxyTargetClass = true)@ComponentScan(basePackages = "com.destiny1020")public class CommonConfiguration {}
  • 1
  • 2
  • 3
  • 4

@EnableAspectJAutoProxy注解中添加屬性proxyTargetClass = true即可。
CGLIB實(shí)現(xiàn)AOP代理的原理是通過動(dòng)態(tài)地創(chuàng)建一個(gè)目標(biāo)Bean的子類來實(shí)現(xiàn)的,該子類的實(shí)例就是AOP代理,它建立起了目標(biāo)Bean到Advice的聯(lián)系。

當(dāng)然還有另外一種解決方案,那就是將方法定義聲明在新創(chuàng)建的接口中并且去掉之前添加的proxyTargetClass = true

@Componentpublic class SampleBean implements SampleInterface {  @Override  public void advicedMethod() {  }  @Override  public void invokeAdvicedMethod() {    advicedMethod();  }}public interface SampleInterface {  void invokeAdvicedMethod();  void advicedMethod();}@Configuration@EnableAspectJAutoProxy@ComponentScan(basePackages = "com.destiny1020")public class CommonConfiguration {}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

這樣就讓業(yè)務(wù)對(duì)象實(shí)現(xiàn)了一個(gè)接口,從而能夠使用基于標(biāo)準(zhǔn)JDK的動(dòng)態(tài)代理來完成Spring AOP代理對(duì)象的創(chuàng)建。

從Debug Stacktrace的角度也可以看出這兩種AOP實(shí)現(xiàn)方式上的區(qū)別:

  • JDK動(dòng)態(tài)代理

  • CGLIB

關(guān)于動(dòng)態(tài)代理和CGLIB這兩種方式的簡要總結(jié)如下:

  • JDK動(dòng)態(tài)代理(Dynamic Proxy)

    • 基于標(biāo)準(zhǔn)JDK的動(dòng)態(tài)代理功能
    • 只針對(duì)實(shí)現(xiàn)了接口的業(yè)務(wù)對(duì)象
  • CGLIB

    • 通過動(dòng)態(tài)地對(duì)目標(biāo)對(duì)象進(jìn)行子類化來實(shí)現(xiàn)AOP代理,上面截圖中的SampleBean$$EnhancerByCGLIB$$1767dd4b即為動(dòng)態(tài)創(chuàng)建的一個(gè)子類
    • 需要指定@EnableAspectJAutoProxy(proxyTargetClass = true)來強(qiáng)制使用
    • 當(dāng)業(yè)務(wù)對(duì)象沒有實(shí)現(xiàn)任何接口的時(shí)候默認(rèn)會(huì)選擇CGLIB

AspectJ

AspectJ是Eclipse旗下的一個(gè)項(xiàng)目。至于它和Spring AOP的關(guān)系,不妨可將Spring AOP看成是Spring這個(gè)龐大的集成框架為了集成AspectJ而出現(xiàn)的一個(gè)模塊。

畢竟很多地方都是直接用到AspectJ里面的代碼。典型的比如@Aspect@Around,@Pointcut注解等等。而且從相關(guān)概念以及語法結(jié)構(gòu)上而言,兩者其實(shí)非常非常相似。比如Pointcut的表達(dá)式語法以及Advice的種類,都是一樣一樣的。

那么,它們的區(qū)別在哪里呢?

最大的區(qū)別在于兩者實(shí)現(xiàn)AOP的底層原理不太一樣:

  • Spring AOP: 基于代理(Proxying)
  • AspectJ: 基于字節(jié)碼操作(Bytecode Manipulation)

用一張圖來表示AspectJ使用的字節(jié)碼操作,就一目了然了:

通過編織階段(Weaving Phase),對(duì)目標(biāo)Java類型的字節(jié)碼進(jìn)行操作,將需要的Advice邏輯給編織進(jìn)去,形成新的字節(jié)碼。畢竟JVM執(zhí)行的都是Java源代碼編譯后得到的字節(jié)碼,所以AspectJ相當(dāng)于在這個(gè)過程中做了一點(diǎn)手腳,讓Advice能夠參與進(jìn)來。

而編織階段可以有兩個(gè)選擇,分別是加載時(shí)編織(也可以成為運(yùn)行時(shí)編織)和編譯時(shí)編織:

加載時(shí)編織(Load-Time Weaving)

顧名思義,這種編織方式是在JVM加載類的時(shí)候完成的。

使用它需要進(jìn)行相關(guān)的配置,舉例如下:

在類路徑的META-INF目錄下創(chuàng)建一個(gè)文件名為aop.xml:

<aspectj>  <weaver>    <include within="com.destiny1020..*" />  </weaver>  <aspects>    <aspect name="com.destiny1020.SampleAspect" />  </aspects></aspectj>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

然后添加啟動(dòng)參數(shù),直接使用AspectJ提供的或者使用Spring提供的工具:

# AspectJ-javaagent:path_to/aspectjweaver-{version}.jar# Spring-javaagent:path_to/org.springframework.instrument-{version}.jar
  • 1
  • 2
  • 3
  • 4
  • 5

當(dāng)使用Spring提供的工具時(shí),還需要進(jìn)行一些配置,以JavaConfig為例:

@Configuration@EnableLoadTimeWeaving@ComponentScan(basePackages = "com.destiny1020")public class CommonConfiguration {}
  • 1
  • 2
  • 3
  • 4

重點(diǎn)就是上述的@EnableLoadTimeWeaving。

編譯時(shí)編織(Compile-Time Weaving)

需要使用AspectJ的編譯器來替換JDK的編譯器??梢越柚鶰aven AspectJ來實(shí)現(xiàn),下面是一例:

<plugin>    <groupId>org.codehaus.mojo</groupId>    <artifactId>aspectj-maven-plugin</artifactId>    <version>1.4</version>    <dependencies>        <dependency>            <groupId>org.aspectj</groupId>            <artifactId>aspectjrt</artifactId>            <version>${aspectj.version}</version>        </dependency>        <dependency>            <groupId>org.aspectj</groupId>            <artifactId>aspectjtools</artifactId>            <version>${aspectj.version}</version>        </dependency>    </dependencies>    <executions>        <execution>            <phase>process-sources</phase>            <goals>                <goal>compile</goal>                <goal>test-compile</goal>            </goals>        </execution>    </executions>    <configuration>        <outxml>true</outxml>        <source>${java.version}</source>        <target>${java.version}</target>    </configuration></plugin>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

然后直接通過mvn test進(jìn)行測(cè)試:

自定義的編譯錯(cuò)誤/警告

舉個(gè)例子,有兩個(gè)Service1和Service2分別位于兩個(gè)包Package1和Package2下,只能在Package2中調(diào)用來自本包內(nèi)部的方法,在Service1中調(diào)用Service2中提供的方法會(huì)導(dǎo)致編譯錯(cuò)誤(能夠用訪問控制符解決的問題強(qiáng)行用這種方式來解決,當(dāng)然只是為了說明問題:)):

@Aspectpublic class EmitCompilationErrorAspect {  @DeclareError("call (* com.destiny1020.biz.package2..*.*(..))"      + "&& !within(com.destiny1020.biz.package2..*)")  public static final String notInBizPackage2 = "只能在Package2中調(diào)用來自Package2的方法";}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
package com.destiny1020.biz.package1;import com.destiny1020.biz.package2.ServiceInPackage2;public class ServiceInPackage1 {  ServiceInPackage2 service2 = new ServiceInPackage2();  public void invokeMethodInPackage2() {    service2.doBizInPackage2();  // 這里理應(yīng)會(huì)出現(xiàn)編譯錯(cuò)誤  }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

實(shí)際情況也正式如此:

在聲明編譯錯(cuò)誤Pointcut的時(shí)候,出現(xiàn)了兩個(gè)新概念:

  • call
  • within

這兩個(gè)新出現(xiàn)的Pointcut原語只能在使用AspectJ作為AOP實(shí)現(xiàn)的時(shí)候才可用。它們表達(dá)的是什么意思呢:

  • call:針對(duì)所有的調(diào)用者(caller),即哪里調(diào)用了Pointcut表達(dá)式匹配的方法,在該方法被執(zhí)行之前就會(huì)被匹配到;而我們經(jīng)常使用的execution則是針對(duì)所有的被調(diào)用方法,而不會(huì)care是誰調(diào)用的該方法
  • within:這個(gè)很好理解,它的Pointcut表達(dá)式是一個(gè)用來匹配完整限定類名的表達(dá)式,比如上例中的!within(com.destiny1020.biz.package2..*)意味不在包com.destiny1020.biz.package2中的類。

在使用AspectJ的編譯時(shí)編織功能時(shí),由于使用了AspectJ Compiler來完成代碼的編譯,因此可以根據(jù)編碼規(guī)范添加相應(yīng)的編譯錯(cuò)誤/警告,來進(jìn)一步地讓代碼更加規(guī)范。這個(gè)特性對(duì)于輔助實(shí)現(xiàn)大型項(xiàng)目的編碼規(guī)范還是很有益處的。

哪種方式更好

先下結(jié)論:It Depends.

得根據(jù)具體需求,不過我個(gè)人認(rèn)為在對(duì)AOP的需求不那么深入和迫切的時(shí)候,使用Spring AOP足矣。

畢竟Spring作為一個(gè)以集成起家的框架,在設(shè)計(jì)Spring AOP的時(shí)候也是為了減輕開發(fā)人員負(fù)擔(dān)而做了不少努力的。它提供的開箱即用(Out-of-the-box)的眾多AOP功能讓很多開發(fā)人員甚至都不知道什么是AOP,就算知道了AOP是Spring的一大基石或者@Transactional和@Cacheable等等常用注解是借助了AOP的力量,但是再深入恐怕就有點(diǎn)勉為其難了。這是優(yōu)點(diǎn)也是缺點(diǎn),當(dāng)需要對(duì)AOP的實(shí)現(xiàn)做出精細(xì)化調(diào)整的時(shí)候,就會(huì)有力不從心的感覺。

這個(gè)時(shí)候,就可以考慮使用AspectJ。AspectJ的功能更加全面和強(qiáng)大。支持全部的Pointcut類型。

這里進(jìn)行了一個(gè)簡單的比較,摘錄并簡單翻譯(括號(hào)內(nèi)是我添加的補(bǔ)充)如下:

Spring-AOP Pros

  • 比AspectJ更簡單,不需要使用Load-Time Weaving以及AspectJ編譯器(為了Compile-Time Weaving)
  • 當(dāng)使用@Aspect注解時(shí)可以很方便的遷移到AspectJ AOP實(shí)現(xiàn)
  • 使用代理模式和裝飾模式

Spring-AOP Cons

  • 由于是基于代理的AOP,所以基本上只能選擇方法execution這一個(gè)Pointcut原語
  • 在類本身中調(diào)用另一個(gè)方法的時(shí)候Aspects不會(huì)生效
  • 有一點(diǎn)運(yùn)行時(shí)的額外開銷
  • 無法為不是從Spring Factory中創(chuàng)建的對(duì)象添加Aspect(只對(duì)Spring Bean有效)

AspectJ Pros

  • 支持所有的Pointcut原語,這意味著你可以做任何事情
  • 運(yùn)行時(shí)開銷比Spring AOP少
  • 能夠添加各種編譯錯(cuò)誤來保障代碼質(zhì)量(這一條是我自己添加的)

AspectJ Cons

  • 當(dāng)心。檢查是否發(fā)生了意料之外的Weaving操作
  • 使用Compile-Time Weaving時(shí)需要額外的構(gòu)建步驟(使用AspectJ Compiler),在使用Load-Time Weaving時(shí)需要一些配置(-javaassist)

參考資料

Spring AOP Reference

AspectJ Quick Reference

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
Spring(三)
《我想進(jìn)大廠》之Spring奪命連環(huán)10問
spring aop術(shù)語解釋
【第六章】 AOP 之 6.1 AOP基礎(chǔ) ——跟我學(xué)spring3
第3章 Spring AOP
3.Spring AOP
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服