AspectJ 是一個(gè)基于 Java 語(yǔ)言的 AOP 框架,它擴(kuò)展了 Java 語(yǔ)言。Spring 2.0 以后,新增了對(duì) AspectJ 方式的支持,新版本的 Spring 框架,建議使用 AspectJ 方式開(kāi)發(fā) AOP。
使用 AspectJ 開(kāi)發(fā) AOP 通常有兩種方式:
基于 XML 的聲明式。基于 Annotation 的聲明式。
基于XML的聲明式
基于 XML 的聲明式是指通過(guò) Spring 配置文件的方式定義切面、切入點(diǎn)及聲明通知,而所有的切面和通知都必須定義在 aop:config 元素中。
下面通過(guò)案例演示 Spring 中如何使用基于 XML 的聲明式實(shí)現(xiàn) AOP 的開(kāi)發(fā)。
1. 導(dǎo)入 JAR 包 使用 AspectJ 除了需要導(dǎo)入 Spring AOP 的 JAR 包以外,還需要導(dǎo)入與 AspectJ 相關(guān)的 JAR 包,具體如下。
spring-aspects-3.2.13.RELEASE.jar:Spring 為 AspectJ 提供的實(shí)現(xiàn),在 Spring 的包中已經(jīng)提供。
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar:是 AspectJ 提供的規(guī)范,可以在官方網(wǎng)址 https://repo.spring.io/webapp/#/search/quick/ 中搜索并下載。
2. 創(chuàng)建切面類(lèi) MyAspect 在 src 目錄下創(chuàng)建一個(gè)名為 com.mengma.aspectj.xml 的包,在該包下創(chuàng)建切面類(lèi) MyAspect,編輯后如下所示。
package com
. mengma
. aspectj
. xml
; import org
. aspectj
. lang
. JoinPoint
;
import org
. aspectj
. lang
. ProceedingJoinPoint
;
public class MyAspect { public void myBefore ( JoinPoint joinPoint
) { System
. out
. print ( "前置通知,目標(biāo):" ) ; System
. out
. print ( joinPoint
. getTarget ( ) + "方法名稱(chēng):" ) ; System
. out
. println ( joinPoint
. getSignature ( ) . getName ( ) ) ; } public void myAfterReturning ( JoinPoint joinPoint
) { System
. out
. print ( "后置通知,方法名稱(chēng):" + joinPoint
. getSignature ( ) . getName ( ) ) ; } public Object
myAround ( ProceedingJoinPoint proceedingJoinPoint
) throws Throwable
{ System
. out
. println ( "環(huán)繞開(kāi)始" ) ; Object obj
= proceedingJoinPoint
. proceed ( ) ; System
. out
. println ( "環(huán)繞結(jié)束" ) ; return obj
; } public void myAfterThrowing ( JoinPoint joinPoint
, Throwable e
) { System
. out
. println ( "異常通知" + "出錯(cuò)了" + e
. getMessage ( ) ) ; } public void myAfter ( ) { System
. out
. println ( "最終通知" ) ; }
}
上述代碼中,分別定義了幾種不同的通知類(lèi)型方法,在這些方法中,通過(guò) JoinPoint 參數(shù)可以獲得目標(biāo)對(duì)象的類(lèi)名、目標(biāo)方法名和目標(biāo)方法參數(shù)等。需要注意的是,環(huán)繞通知必須接收一個(gè)類(lèi)型為 ProceedingJoinPoint 的參數(shù),返回值必須是 Object 類(lèi)型,且必須拋出異常 。異常通知中可以傳入 Throwable 類(lèi)型的參數(shù),用于輸出異常信息。
3. 創(chuàng)建 Spring 配置文件 在 com.mengma.aspectj.xml 包下創(chuàng)建 applicationContext.xml 的配置文件,如下所示。
< ? xml version
= "1.0" encoding
= "UTF-8" ? >
< beans xmlns
= "http://www.springframework.org/schema/beans" xmlns
: xsi
= "http://www.w3.org/2001/XMLSchema-instance" xmlns
: aop
= "http://www.springframework.org/schema/aop" xsi
: schemaLocation
= " http
: / / www
. springframework
. org
/ schema
/ beanshttp
: / / www
. springframework
. org
/ schema
/ beans
/ spring
- beans
- 2.5 . xsd http
: / / www
. springframework
. org
/ schema
/ aophttp
: / / www
. springframework
. org
/ schema
/ aop
/ spring
- aop
- 2.5 . xsd"
> < ! -- 目標(biāo)類(lèi)
-- > < bean id
= "customerDao" class = "com.mengma.dao.CustomerDaoImpl" / > < ! -- 切面類(lèi)
-- > < bean id
= "myAspect" class = "com.mengma.aspectj.xml.MyAspect" > < / bean
> < ! -- AOP 編程
-- > < aop
: config
> < aop
: aspect ref
= "myAspect" > < ! -- 配置切入點(diǎn),通知最后增強(qiáng)哪些方法
-- > < aop
: pointcut expression
= "execution ( * com.mengma.dao.*.* (..))" id
= "myPointCut" / > < ! -- 前置通知,關(guān)聯(lián)通知 Advice和切入點(diǎn)PointCut
-- > < aop
: before method
= "myBefore" pointeut
- ref
= "myPointCut" / > < ! -- 后置通知,在方法返回之后執(zhí)行,就可以獲得返回值returning 屬性
-- > < aop
: after
- returning method
= "myAfterReturning" pointcut
- ref
= "myPointCut" returning
= "returnVal" / > < ! -- 環(huán)繞通知
-- > < aop
: around method
= "myAround" pointcut
- ref
= "myPointCut" / > < ! -- 拋出通知:用于處理程序發(fā)生異常,可以接收當(dāng)前方法產(chǎn)生的異常
-- > < ! -- * 注意:如果程序沒(méi)有異常,則不會(huì)執(zhí)行增強(qiáng)
-- > < ! -- * throwing屬性:用于設(shè)置通知第二個(gè)參數(shù)的名稱(chēng),類(lèi)型Throwable
-- > < aop
: after
- throwing method
= "myAfterThrowing" pointcut
- ref
= "myPointCut" throwing
= "e" / > < ! -- 最終通知:無(wú)論程序發(fā)生任何事情,都將執(zhí)行
-- > < aop
: after method
= "myAfter" pointcut
- ref
= "myPointCut" / > < / aop
: aspect
> < / aop
: config
>
< / beans
>
上述代碼中,首先在第 4、7、8 行代碼中分別導(dǎo)入了 AOP 的命名空間。第 12 行代碼指定了切面類(lèi)。
第 17、18 行代碼配置了切入點(diǎn),通知需要增強(qiáng)哪些方法,expression="execution(com.mengma.dao..*(…))的意思是增強(qiáng) com.mengma.dao 包下所有的方法。
第 20~32 行代碼用于關(guān)聯(lián)通知(Advice)和切入點(diǎn)(PointCut)。以第 20 行代碼前置通知為例,aop:before 標(biāo)簽的 method 屬性用于指定通知,pointcut-ref 屬性用于指定切入點(diǎn),也就是要增強(qiáng)的方法。
4. 創(chuàng)建測(cè)試類(lèi) 在 com.mengma.aspectj.xml 包下創(chuàng)建測(cè)試類(lèi) XMLTest,如下所示。
package com
. mengma
. aspectj
. xml
; import org
. junit
. Test
;
import org
. springframework
. context
. ApplicationContext
;
import org
. springframework
. context
. support
. ClassPathXmlApplicationContext
;
import com
. mengma
. dao
. CustomerDao
; public class XMLTest { @Test public void test ( ) { String xmlPath
= "com/mengma/aspectj/xml/applicationContext.xml" ; ApplicationContext applicationContext
= new ClassPathXmlApplicationContext ( xmlPath
) ; CustomerDao customerDao
= ( CustomerDao
) applicationContext
. getBean ( "customerDao" ) ; customerDao
. add ( ) ; }
}
5. 運(yùn)行項(xiàng)目并查看結(jié)果 使用 JUnit 測(cè)試運(yùn)行 test() 方法,運(yùn)行成功后,控制臺(tái)的輸出結(jié)果如下圖所示。
為了更好地演示異常通知,接下來(lái)在 CustomerDaoImpl 類(lèi)的 add() 方法中添加一行會(huì)拋出異常的代碼,如“int i=1/0;”,重新運(yùn)行 XMLTest 測(cè)試類(lèi),可以看到異常通知執(zhí)行了。
基于 Annotation 的聲明式
在 Spring 中,盡管使用 XML 配置文件可以實(shí)現(xiàn) AOP 開(kāi)發(fā),但是如果所有的相關(guān)的配置都集中在配置文件中,勢(shì)必會(huì)導(dǎo)致 XML 配置文件過(guò)于臃腫,從而給維護(hù)和升級(jí)帶來(lái)一定的困難。
為此,AspectJ 框架為 AOP 開(kāi)發(fā)提供了另一種開(kāi)發(fā)方式——基于 Annotation 的聲明式。AspectJ 允許使用注解定義切面、切入點(diǎn)和增強(qiáng)處理,而 Spring 框架則可以識(shí)別并根據(jù)這些注解生成 AOP 代理。
Annotation 注解介紹
名稱(chēng)說(shuō)明 @Aspect 用于定義一個(gè)切面。 @Before 用于定義前置通知,相當(dāng)于 BeforeAdvice。 @AfterReturning 用于定義后置通知,相當(dāng)于 AfterReturningAdvice。 @Around 用于定義環(huán)繞通知,相當(dāng)于MethodInterceptor。 @AfterThrowing 用于定義拋出通知,相當(dāng)于ThrowAdvice。 @After 用于定義最終final通知,不管是否異常,該通知都會(huì)執(zhí)行。 @DeclareParents 用于定義引介通知,相當(dāng)于IntroductionInterceptor。
下面使用注解的方式重新實(shí)現(xiàn)基于XML的聲明式部分的功能。 1. 創(chuàng)建切面類(lèi) MyAspect 在 src 目錄下創(chuàng)建一個(gè)名為 com.mengma.aspectj.annotation 的包,在該包下創(chuàng)建一個(gè)切面類(lèi) MyAspect,如下所示。
package com
. mengma
. aspectj
. annotation
; import org
. aspectj
. lang
. JoinPoint
;
import org
. aspectj
. lang
. ProceedingJoinPoint
;
import org
. aspectj
. lang
. annotation
. After
;
import org
. aspectj
. lang
. annotation
. AfterReturning
;
import org
. aspectj
. lang
. annotation
. AfterThrowing
;
import org
. aspectj
. lang
. annotation
. Around
;
import org
. aspectj
. lang
. annotation
. Aspect
;
import org
. aspectj
. lang
. annotation
. Before
;
import org
. aspectj
. lang
. annotation
. Pointcut
;
import org
. springframework
. stereotype
. Component
;
@Aspect
@Component
public class MyAspect { @Pointcut ( "execution(*com.mengma.dao..*.*(..))" ) private void myPointCut ( ) { } @Before ( "myPointCut()" ) public void myBefore ( JoinPoint joinPoint
) { System
. out
. print ( "前置通知,目標(biāo):" ) ; System
. out
. print ( joinPoint
. getTarget ( ) + "方法名稱(chēng):" ) ; System
. out
. println ( joinPoint
. getSignature ( ) . getName ( ) ) ; } @AfterReturning ( value
= "myPointCut()" ) public void myAfterReturning ( JoinPoint joinPoint
) { System
. out
. print ( "后置通知,方法名稱(chēng):" + joinPoint
. getSignature ( ) . getName ( ) ) ; } @Around ( "myPointCut()" ) public Object
myAround ( ProceedingJoinPoint proceedingJoinPoint
) throws Throwable
{ System
. out
. println ( "環(huán)繞開(kāi)始" ) ; Object obj
= proceedingJoinPoint
. proceed ( ) ; System
. out
. println ( "環(huán)繞結(jié)束" ) ; return obj
; } @AfterThrowing ( value
= "myPointCut()" , throwing
= "e" ) public void myAfterThrowing ( JoinPoint joinPoint
, Throwable e
) { System
. out
. println ( "異常通知" + "出錯(cuò)了" + e
. getMessage ( ) ) ; } @After ( "myPointCut()" ) public void myAfter ( ) { System
. out
. println ( "最終通知" ) ; }
}
上述代碼中,第 13 行 @Aspect 注解用于聲明這是一個(gè)切面類(lèi),該類(lèi)作為組件使用,所以要添加 @Component 注解才能生效。第 19 行中 @Poincut 注解用于配置切入點(diǎn),取代 XML 文件中配置切入點(diǎn)的代碼。
在每個(gè)通知相應(yīng)的方法上都添加了注解聲明,并且將切入點(diǎn)方法名“myPointCut”作為參數(shù)傳遞給要執(zhí)行的方法,如需其他參數(shù)(如異常通知的異常參數(shù)),可以根據(jù)代碼提示傳遞相應(yīng)的屬性值。
2. 為目標(biāo)類(lèi)添加注解 在 com.mengma.dao.CustomerDaoImpl 目標(biāo)類(lèi)中添加注解 @Repository(“customerDao”)。
3. 創(chuàng)建Spring配置文件 在 com.mengma.aspectj.annotation 包下創(chuàng)建 applicationContext.xml 配置文件,如下所示。
< ? xml version
= "1.0" encoding
= "UTF-8" ? >
< beans xmlns
= "http://www.springframework.org/schema/beans" xmlns
: xsi
= "http://www.w3.org/2001/XMLSchema-instance" xmlns
: aop
= "http://www.springframework.org/schema/aop" xmlns
: context
= "http://www.springframework.org/schema/context" xsi
: schemaLocation
= "http
: / / www
. springframework
. org
/ schema
/ beanshttp
: / / www
. springframework
. org
/ schema
/ beans
/ spring
- beans
- 2.5 . xsdhttp
: / / www
. springframework
. org
/ schema
/ aophttp
: / / www
. springframework
. org
/ schema
/ aop
/ spring
- aop
- 2.5 . xsdhttp
: / / www
. springframework
. org
/ schema
/ contexthttp
: / / www
. springframework
. org
/ schema
/ context
/ spring
- context
. xsd"
> < ! -- 掃描含com
. mengma包下的所有注解
-- > < context
: component
- scan base
- package = "com.mengma" / > < ! -- 使切面開(kāi)啟自動(dòng)代理
-- > < aop
: aspectj
- autoproxy
> < / aop
: aspectj
- autoproxy
>
< / beans
>
上述代碼中,首先導(dǎo)入了 AOP 命名空間及其配套的約束,使切面類(lèi)中的 @AspectJ 注解能夠正常工作;第 13 行代碼添加了掃描包,使注解生效。需要注意的是,這里還包括目標(biāo)類(lèi) com.mengma.dao.CustomerDaoImpl 的注解,所以 base-package 的值為 com.mengma;第 15 行代碼的作用是切面開(kāi)啟自動(dòng)代理。
4. 創(chuàng)建測(cè)試類(lèi) 在 com.mengma.aspectj.annotation 包下創(chuàng)建一個(gè)名為 AnnotationTest 的測(cè)試類(lèi),如下所示。
package com
. mengma
. aspectj
. annotation
; import org
. junit
. Test
;
import org
. springframework
. context
. ApplicationContext
;
import org
. springframework
. context
. support
. ClassPathXmlApplicationContext
; import com
. mengma
. dao
. CustomerDao
; public class AnnotationTest { @Test public void test ( ) { String xmlPath
= "com/mengma/aspectj/xml/applicationContext.xml" ; ApplicationContext applicationContext
= new ClassPathXmlApplicationContext ( xmlPath
) ; CustomerDao customerDao
= ( CustomerDao
) applicationContext
. getBean ( "customerDao" ) ; customerDao
. add ( ) ; }
}
5. 運(yùn)行項(xiàng)目并查看結(jié)果 使用 JUnit 測(cè)試運(yùn)行 test() 方法,運(yùn)行成功后,控制臺(tái)的輸出結(jié)果如下圖所示。
刪除 add() 方法中的“int i=1/0;”,重新運(yùn)行 test() 方法,此時(shí)控制臺(tái)的輸出結(jié)果如下圖所示。
從輸出結(jié)果中可以看出,已成功使用 Annotation 的方式實(shí)現(xiàn)了 AOP 開(kāi)發(fā)。與其他方式相比,基于 Annotation 方式實(shí)現(xiàn) AOP 的效果是最方便的方式。
總結(jié)
以上是生活随笔 為你收集整理的Spring使用AspectJ开发AOP 的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
如果覺(jué)得生活随笔 網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔 推薦給好友。