Spring AOP AspectJ使用及配置過程解析
這篇文章主要介紹了Spring AOP AspectJ使用及配置過程解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
AspectJ是一個基于Java語言的AOP框架,Spring2.0以后新增了對AspectJ切點表達式支持。因為Spring1.0的時候Aspectj還未出現(xiàn);
AspectJ1.5中新增了對注解的支持,允許直接在Bean類中定義切面。新版本的Spring框架建
議我們都使用AspectJ方式來開發(fā)AOP,并提供了非常靈活且強大的切點表達式 ;
當然無論使用Spring自己的AOP還是AspectJ相關(guān)的概念都是相同的;
注解配置
依賴導入:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.2.RELEASE</version> </dependency>
通知類型
- @AspectJ提供的通知類型:
- @Before 前置通知 在原始方法執(zhí)行前執(zhí)行
- @AfterReturning 后置通知 在原始方法執(zhí)行前執(zhí)行
- @Around 環(huán)繞通知 徹底攔截原始方法的執(zhí)行,執(zhí)行前后都可以增加邏輯,也可以不執(zhí)行原始方法
- @AfterThrowing拋出通知,執(zhí)行原始方法出現(xiàn)異常時執(zhí)行
- @After 最終final通知,不管是否異常,原始方法調(diào)用后都會執(zhí)行
- @DeclareParents 引介通知,相當于IntroductionInterceptor (了解即可)
定義切點
通過execution函數(shù)來定義切點
語法:execution(訪問修飾符 返回類型 方法名 參數(shù) 異常)
表達式示例:
- 匹配所有類public方法:execution(public * *(..))第一個*表示返回值 ..表示任意個任意類型參數(shù)
- 匹配指定包下所有方法: execution(* cn.xxx.dao.*(..)) 第一個想*表示忽略權(quán)限和返回值類型
- 匹配指定包下所有方法:execution(* cn.xxx.dao..*(..))包含子包
- 匹配指定類所有方法: execution(* cn.xxx.service.UserService.*(..))
- 匹配實現(xiàn)特定接口所有類方法 : execution(* cn.xxx.dao.GenericDAO+.*(..))
- 匹配所有save開頭的方法: execution(* save*(..))
前置通知
pom依賴:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.2.RELEASE</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.2.RELEASE</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
xml需要添加aop名稱空間及xsd:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 啟用aspectj -->
<aop:aspectj-autoproxy/>
<!-- 目標-->
<bean id="personDao" class="com.yh.demo1.PersonDao"/>
<!-- 切面-->
<bean class="com.yh.demo1.MyAspect"/>
</beans>
test:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Test1 {
@Autowired
PersonDao personDao;
@Test
public void test(){
personDao.delete();
personDao.update();
}
}
切面類:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class MyAspect {
//表示PersonDao下所有方法都作為切點
@Before(value = "execution(* com.yh.demo1.PersonDao.*(..))")
public void beforeAdvice(){
System.out.println("before code run.....");
}
}
當我們需要獲取切點信息(被增強的代碼)時,可以在通知添加參數(shù),想下面這樣
@Aspect
public class MyAspect {
@Before(value = "execution(* com.yh.demo1.PersonDao.*(..))")
public void beforeAdvice2(JoinPoint point){
System.out.println("before code run2....." + point);
}
}
后置通知:
//當需要獲取原始方法的返回值時可以在注解中添加returning參數(shù)來指定參數(shù)名 Aspectj會自動將返回值放到參數(shù)中
@AfterReturning(value = "execution(* com.yh.demo1.PersonDao.delete(..))",returning = "result")
public void afterAdvice(Object result){
System.out.println("刪除方法執(zhí)行后 ..... 返回值為:"+ result);
}
后置通知可以獲取目標方法的返回值
環(huán)繞通知:
@Around(value = "execution(* com.yh.demo1.PersonDao.insert(..))")
public void aroundAdvice(ProceedingJoinPoint point) throws Throwable {
//code............
System.out.println("環(huán)繞前置..");
//執(zhí)行原始方法 __當需要獲取返回值時可以聲明變量接收
Object result = point.proceed();
System.out.println("原始方法返回值: "+result);
//code............
System.out.println("環(huán)繞后置..");
}
環(huán)繞通知與其他通知最大的區(qū)別在于環(huán)繞通知可以控制是否調(diào)用原始方法
注意:參數(shù)類型必須為ProceedingJoinPoint,否則 無法執(zhí)行原始方法,
異常通知
@AfterThrowing(value = "execution(* com.yh.demo1.PersonDao.save(..))",throwing = "e")
public void exceptionHandler(JoinPoint point,Exception e){
System.out.println(point + " 方法出現(xiàn)"+e.getMessage()+"異常");
}
當方法中出現(xiàn)時才會執(zhí)行該通知,若需要獲取異常信息,可在注解中添加throwing指定參數(shù)名稱
我們可以使用環(huán)繞+異常通知來處理數(shù)據(jù)庫事務(wù),在環(huán)繞中開啟事務(wù)以及提交事務(wù),異常通知中回滾事務(wù),當然Spring已經(jīng)對事務(wù)進行了封裝不需要自己寫
最終通知
@After(value = "execution(* *delete(..))")
public void afterRun(){
System.out.println("最終");
}
最終通知叫做after 即調(diào)用原始方法之后執(zhí)行無論原始方法中是否出現(xiàn)異常
而后置叫做afterReturning表示在成功返回后才會執(zhí)行執(zhí)行
帶有邏輯符的表達式:
在表達式中可以使用戶邏輯操運算符,與&& 或|| 非!
示例:
/* 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(..)) */ 2|4切點命名
切點命名
假設(shè)有多個通知應用在同一個切點上時,我們需要重復編寫execution表達式,且后續(xù)要修改切點時則多個通知都需要修改,維護起來非常麻煩,我們可以通過給切點指定名稱從而完成對切點的重復使用和統(tǒng)一操作,以提高開發(fā)維護效率;
//定義命名切點 方法名稱即切點名稱
@Pointcut(value = "execution(* com.yh.demo1.PersonDao.save(..))")
private void savePointcut(){}
@Pointcut(value = "execution(* com.yh.demo1.PersonDao.delete(..))")
private void deletePointcut(){}
多個通知應用到同一個切點:
//使用命名切點
@Before(value = "savePointcut()")
public void beforeAdvice(){
System.out.println("before code run.....");
}
//使用命名切點
@Around(value = "savePointcut()")
public void beforeAdvice2(ProceedingJoinPoint point) throws Throwable {
System.out.println("環(huán)繞前");
point.proceed();
System.out.println("環(huán)繞后");
一個通知應用到多個切點
//同一個通知對應多個切點
@After(value = "savePointcut()||deletePointcut()")
public void afterAdvice(){
System.out.println("after code run.....");
}
XML配置
XML配置所需的jar 以及各個對象之間的依賴關(guān)以及表達式的寫法都是一樣的,僅僅是換種方式來寫而已;
xml:
<!--目標-->
<bean id="studentDao" class="com.yh.demo2.StudentDao"/>
<!--通知-->
<bean id="advices" class="com.yh.demo2.XMLAdvice"/>
<!--織入信息-->
<aop:config>
<!--切點定義-->
<aop:pointcut id="select" expression="execution(* com.yh.demo2.StudentDao.select(..))"/>
<!--切面定義-->
<aop:aspect ref="advices">
<aop:before method="before" pointcut-ref="select"/>
<aop:after-returning method="afterReturning" pointcut-ref="select" returning="result"/>
<aop:after method="after" pointcut-ref="select" />
<aop:after-throwing method="exception" pointcut-ref="select" throwing="e"/>
<aop:around method="around" pointcut-ref="select"/>
</aop:aspect>
<!--入侵式通知 即通知需要實現(xiàn)指定接口 兩種不能同時使用 -->
<aop:advisor advice-ref="advice2" pointcut-ref="select"/>
</aop:config>
<!--入侵式通知Bean-->
<bean id="advice2" class="com.yh.demo2.XMLAdvice2"/>
通知類:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.JoinPoint;
public class XMLAdvice {
public void before(JoinPoint pointcut){ System.out.println("前置通知 切點:"+pointcut); }
public void afterReturning(JoinPoint point,Object result){
System.out.println("后置通知 切點:"+point);
}
public void after(JoinPoint point){ System.out.println("最終通知 切點:"+point); }
public void exception(JoinPoint point,Throwable e){
System.out.println("異常通知: " + e+"切點:"+point);
}
public void around(ProceedingJoinPoint point) throws Throwable {
System.out.println("環(huán)繞前");
point.proceed();
System.out.println("環(huán)繞后");
}
}
你會發(fā)現(xiàn) ,無論是XML還是注解都不需要手動指定代理,以及目標對象,Aspectj會從切點中獲取目標對象信息并自動創(chuàng)建代理;
AspectJ是目前更流行的方式,具體采用XML還是注解需要根據(jù)項目具體情況,小組協(xié)作開發(fā)推薦xml;
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot?整合數(shù)據(jù)源的具體實踐
本文主要介紹了SpringBoot?整合數(shù)據(jù)源的具體實踐,利用?Spring?Boot?的自動配置和簡化的注解來簡化數(shù)據(jù)源配置工作,從而更專注于應用程序的業(yè)務(wù)邏輯開發(fā),感興趣的可以了解一下2023-11-11
idea創(chuàng)建spring?boot項目時javaversion只能選擇17和21解決辦法
這篇文章主要給大家介紹了關(guān)于idea創(chuàng)建spring?boot項目時javaversion只能選擇17和21的解決辦法,文中通過代碼介紹的非常詳細,對大家學習或者工作具有一定的參考借鑒價值,需要的朋友可以參考下2024-01-01
Java中ArrayBlockingQueue和LinkedBlockingQueue
這篇文章主要介紹了Java中ArrayBlockingQueue和LinkedBlockingQueue,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的朋友可以參考一下2022-09-09
SpringBoot啟動后立即執(zhí)行的幾種方法小結(jié)
在項目開發(fā)中某些場景必須要用到啟動項目后立即執(zhí)行方式的功能,本文主要介紹了SpringBoot啟動后立即執(zhí)行的幾種方法小結(jié),具有一定的參考價值,感興趣的可以了解一下2023-05-05

