Aspectj是什麼?
是一個基於java語言的AOP框架。搭配的有自己創建的編譯器,支持Java語法,也創建一套自有的語法,通過操作字節碼產生代理對象,專門用於AOP。
Spring2.0以後新增了切點表達式支持,
AspectJ1.5中新增了對註解的支持,允許直接在Bean類中定義切面。
新版本的Spring框架建議我們都使用AspectJ方式來開發AOP,並提供了非常靈活且強大的切點表達式 ;
當然無論使用Spring自己的AOP還是AspectJ相關的概念都是相同的;
但是無論是哪種方式,關鍵點都在於切點和通知
註解配置
導入依賴:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
通知類型
首先需要創建一個普通類作爲通知類,@AspectJ用於標註其屬於通知類,見下面案例:
@Before 前置通知 在原始方法執行前執行
@AfterReturning 後置通知 在原始方法執行後執行
@Around 環繞通知 徹底攔截原始方法的執行,執行前後都可以增加邏輯,也可以不執行原始方法
@AfterThrowing拋出通知,執行原始方法出現異常時執行
@After 最終final通知,不管是否異常,原始方法調用後都會執行
@DeclareParents 引介通知,相當於IntroductionInterceptor (瞭解即可)
定義切點
通過execution函數來定義切點
語法:execution(權限修飾符 返回類型 方法名 (參數列表) 異常)
注意:如果某一項不限制條件,則使用*表示
如果 權限修飾符 和 返回類型 都是*,只寫一個即可。
如果全都不限如下:
execution(* *(…))
切點表達式示例
匹配所有類public方法:execution(public * *(..))
第一個*表示返回值 …表示任意個任意類型的參數
匹配指定包下所有類所有方法: execution(* cn.xxx.dao.*.*(..))
第一個想*表示忽略權限和返回值類型
匹配指定包下所有類所有方法:execution(* cn.xxx.dao..*(..))
包含子包
匹配指定類所有方法: execution(* cn.xxx.service.UserService.*(..))
匹配實現特定接口所有類方法 :execution(* cn.xxx.dao.GenericDAO+.*(..))
匹配所有save開頭的方法: execution(* save*(..))
匹配某個指定的方法: execution(* com.yh.dao.StudentService.save(..))
具體使用
準備工作
pom依賴:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>compile</scope>
</dependency>
<!-- Maven會自動下載所有Spring核心容器和aop的依賴-->
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
</dependencies>
接口:
public interface PersonDao {
void haha();
void select();
void update();
void delete();
}
實現類:
public class PersonDaoImpl implements PersonDao {
public void haha() {
System.out.println("哈哈哈哈哈哈哈");
}
public void select() {
System.out.println("查詢!");
}
public void update() {
System.out.println("更新!");
}
public void delete() {
System.out.println("刪除!");
}
}
切面類:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class MyAspect {
//所有通知註解,都必須同時指定切點表達式 用於匹配方法
//public void 方法名稱(參數列表,填的是參數類型) * 表示通配符
//(..)表示任意個數的任意類型參數都可以
@Before("execution(* *(..))")
public void before(){
System.out.println("前置通知執行了");
}
}
配置applicationContext.xml
<!--目標類-->
<bean id="personDao" class="cx.dao.PersonDaoImpl"/>
<!--配置切面-->
<bean id="myAspect" class="cx.dao.MyAspect"/>
<!--啓用AspectJ-->
<aop:aspectj-autoproxy/>
測試:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class test1 {
@Autowired
private PersonDao personDao;
@Test
public void test1(){
personDao.haha();
personDao.delete();
personDao.select();
personDao.update();
}
}
餘下的後置通知:
@AfterReturning("execution(* cx.dao.*.*t(..))")
public void afterReturning() {
System.out.println("dao包下的所有最後一個帶t的方法執行後置通知");
}
環繞通知:
@Around("execution(* *.e(..))")
public Object around(ProceedingJoinPoint proceedingJoinPoint) {
System.out.println("環繞前");
try {
Object proceed = proceedingJoinPoint.proceed();
System.out.println("環繞後");
return proceed;
} catch (Throwable throwable) {
throwable.printStackTrace();
return null;
}
}
環繞通知與其他通知最大的區別在於環繞通知可以控制是否調用原始方法
注意:參數類型必須爲ProceedingJoinPoint,否則 無法執行原始方法,
異常通知:
@AfterThrowing(value = "execution(* *haha(..))", throwing = "e")
public void throwing(Exception e) {
System.out.println("異常" + e);
}
模擬異常:
public void haha() {
int i = 1 / 0;
System.out.println("哈哈哈哈哈哈哈");
}
當方法中出現異常時纔會執行該通知,若需要獲取異常信息,可在註解中添加throwing指定參數名稱
我們可以使用環繞+異常通知來處理數據庫事務,在環繞中開啓事務以及提交事務,異常通知中回滾事務,當然Spring已經對事務進行了封裝不需要自己寫
上面出現異常你會發現後置通知沒有執行,因爲後置通知是隻有正確返回時纔會執行。
最終通知:
//下面是不論何種權限和返回值,以haha結束的方法 任意參數列表
@After("execution(* *haha(..))")
public void after() {
System.out.println("最終通知");
}
上面代碼可以知道,雖然出現異常停止執行,但是最終通知依然執行了。
邏輯操運算符的表達式
在表達式中可以使用戶邏輯操運算符,與&& 或|| 非!
示例:
/*
execution(* cn.xxx.service.UserDao.insert(..))||execution(* cn.xxx.service.UserDao.delete(..))
execution(* cn.xxx.service.UserDao.*nsert(..))&&execution(* cn.xxx.service.UserDao.inser*(..))
!execution(* cn.xxx.service.UserDao.insert(..))
*/
切點命名
假設有多個通知應用在同一個切點上時,我們需要重複編寫execution表達式,且後續要修改切點時則多個通知都需要修改,維護起來非常麻煩,可以通過給切點指定名稱從而完成對切點的重複使用和統一操作,以提高開發維護效率。
//定義命名切點 方法名稱即切點名稱
@Pointcut(value = "execution(* *delete(..))")
public void pointDemo(){
}
@Pointcut(value = "execution(* *ha(..))")
public void pointDemo2(){
}
多個通知應用到同一個切點:
@After(value = "pointDemo()")
public void after() {
System.out.println("最終通知");
}
@Around(value = "pointDemo()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) {
System.out.println("環繞前");
try {
Object proceed = proceedingJoinPoint.proceed();
System.out.println("環繞後");
return proceed;
} catch (Throwable throwable) {
throwable.printStackTrace();
return null;
}
}
一個通知應用多個切點:
@Before(value = "pointDemo()||pointDemo2()")
public void before() {
System.out.println("前置通知執行了");
}
xml配置
XML配置所需的jar 以及各個對象之間的依賴關係以及表達式的寫法都是一樣的,僅僅是換種方式來寫而已;
通知類:
public class MyAdvice2 {
public void beforeAdvice() {
System.out.println("我是前置通知");
}
public void afterReturningAdvice() {
System.out.println("我是後置通知");
}
public void afterAdvice() {
System.out.println("我是最終通知");
}
public void throwingAdvice(Exception e) {
System.out.println("我是異常通知,因爲" + e);
}
public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
System.out.println("環繞前");
try {
Object proceed = proceedingJoinPoint.proceed();
System.out.println("環繞後");
return proceed;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}
}
配置文件:applicationContext.xml
<!--目標類-->
<bean id="personDao" class="cx.dao.PersonDaoImpl"/>
<!--通知-->
<bean id="myAspect2" class="cx.dao.MyAdvice2"/>
<!--配置AOP-->
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* *t(..))"/>
<aop:aspect ref="myAspect2">
<aop:after method="afterAdvice" pointcut-ref="pointcut"/>
<aop:before method="beforeAdvice" pointcut-ref="pointcut"/>
<aop:around method="aroundAdvice" pointcut-ref="pointcut"/>
<aop:after-returning method="afterReturningAdvice" pointcut-ref="pointcut"/>
<aop:after-throwing method="throwingAdvice" pointcut-ref="pointcut" throwing="e"/>
</aop:aspect>
</aop:config>
無論是XML還是註解都不需要手動指定代理,以及目標對象,Aspectj會從切點中獲取目標對象信息並自動創建代理。