Spring AOP AspectJ使用及配置過程解析

 更新時間:2020年01月13日 14:35:15   投稿:yaominghui   我要評論
這篇文章主要介紹了Spring AOP AspectJ使用及配置過程解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下

這篇文章主要介紹了Spring AOP AspectJ使用及配置過程解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下

AspectJ是一個基于Java語言的AOP框架,Spring2.0以后新增了對AspectJ切點表達式支持。因為Spring1.0的時候Aspectj還未出現;

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(訪問修飾符 返回類型 方法名 參數 異常)

表達式示例:

  • 匹配所有類public方法:execution(public * *(..))第一個*表示返回值 ..表示任意個任意類型參數
  • 匹配指定包下所有方法: execution(* cn.xxx.dao.*(..)) 第一個想*表示忽略權限和返回值類型
  • 匹配指定包下所有方法:execution(* cn.xxx.dao..*(..))包含子包
  • 匹配指定類所有方法: execution(* cn.xxx.service.UserService.*(..))
  • 匹配實現特定接口所有類方法 : 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.....");
  }
}

當我們需要獲取切點信息(被增強的代碼)時,可以在通知添加參數,想下面這樣

@Aspect
public class MyAspect {
  @Before(value = "execution(* com.yh.demo1.PersonDao.*(..))")
  public void beforeAdvice2(JoinPoint point){
    System.out.println("before code run2....." + point);
  }
}

后置通知:

//當需要獲取原始方法的返回值時可以在注解中添加returning參數來指定參數名 Aspectj會自動將返回值放到參數中
@AfterReturning(value = "execution(* com.yh.demo1.PersonDao.delete(..))",returning = "result")
public void afterAdvice(Object result){
  System.out.println("刪除方法執行后 .....  返回值為:"+ result);
}

后置通知可以獲取目標方法的返回值

環繞通知:

@Around(value = "execution(* com.yh.demo1.PersonDao.insert(..))")
public void aroundAdvice(ProceedingJoinPoint point) throws Throwable {
  //code............
  System.out.println("環繞前置..");

  //執行原始方法 __當需要獲取返回值時可以聲明變量接收
  Object result = point.proceed();
  System.out.println("原始方法返回值: "+result);
  //code............
  System.out.println("環繞后置..");
}

環繞通知與其他通知最大的區別在于環繞通知可以控制是否調用原始方法

注意:參數類型必須為ProceedingJoinPoint,否則 無法執行原始方法,

異常通知

@AfterThrowing(value = "execution(* com.yh.demo1.PersonDao.save(..))",throwing = "e")
public void exceptionHandler(JoinPoint point,Exception e){
  System.out.println(point + " 方法出現"+e.getMessage()+"異常");
}

當方法中出現時才會執行該通知,若需要獲取異常信息,可在注解中添加throwing指定參數名稱

我們可以使用環繞+異常通知來處理數據庫事務,在環繞中開啟事務以及提交事務,異常通知中回滾事務,當然Spring已經對事務進行了封裝不需要自己寫

最終通知

@After(value = "execution(* *delete(..))")
public void afterRun(){
  System.out.println("最終");
}

最終通知叫做after 即調用原始方法之后執行無論原始方法中是否出現異常

而后置叫做afterReturning表示在成功返回后才會執行執行

帶有邏輯符的表達式:
在表達式中可以使用戶邏輯操運算符,與&& 或|| 非!

示例:

/*
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切點命名

切點命名

假設有多個通知應用在同一個切點上時,我們需要重復編寫execution表達式,且后續要修改切點時則多個通知都需要修改,維護起來非常麻煩,我們可以通過給切點指定名稱從而完成對切點的重復使用和統一操作,以提高開發維護效率;

//定義命名切點 方法名稱即切點名稱
@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("環繞前");
  point.proceed();
  System.out.println("環繞后");

一個通知應用到多個切點

//同一個通知對應多個切點
@After(value = "savePointcut()||deletePointcut()")
public void afterAdvice(){
  System.out.println("after code run.....");
}

XML配置

XML配置所需的jar 以及各個對象之間的依賴關以及表達式的寫法都是一樣的,僅僅是換種方式來寫而已;

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>
   
    <!--入侵式通知 即通知需要實現指定接口 兩種不能同時使用 -->
    <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("環繞前");
    point.proceed();
    System.out.println("環繞后");
  }
}

你會發現 ,無論是XML還是注解都不需要手動指定代理,以及目標對象,Aspectj會從切點中獲取目標對象信息并自動創建代理;

AspectJ是目前更流行的方式,具體采用XML還是注解需要根據項目具體情況,小組協作開發推薦xml;

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

相關文章

  • Java List雙擊事件實現方法

    Java List雙擊事件實現方法

    這篇文章主要介紹了Java List雙擊事件實現方法,需要的朋友可以參考下
    2014-09-09
  • springboot注冊bean的三種方法

    springboot注冊bean的三種方法

    這篇文章主要介紹了springboot注冊bean的三種方法,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-02-02
  • Mybatis傳遞多個參數的解決辦法(三種)

    Mybatis傳遞多個參數的解決辦法(三種)

    這篇文章主要介紹了Mybatis傳遞多個參數的解決辦法(三種),個人覺得第三種解決辦法比較好用,有需要的朋友一起學習吧
    2016-05-05
  • java課程設計之坦克大戰

    java課程設計之坦克大戰

    這篇文章主要為大家詳細介紹了java課程設計之坦克大戰,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-12-12
  • MyBatis快速入門

    MyBatis快速入門

    MyBatis是支持普通SQL查詢,存儲過程和高級映射的優秀持久層框架。MyBatis消除了幾乎所有的JDBC代碼和參數的手工設置以及結果集的檢索。想要學好它,那就要從MyBatis基礎知識學起,下面跟著小編一起來看下吧
    2017-03-03
  • JAVA 添加、修改和刪除PDF書簽的示例代碼

    JAVA 添加、修改和刪除PDF書簽的示例代碼

    這篇文章主要介紹了JAVA 添加、修改和刪除PDF書簽的示例代碼,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-06-06
  • Java面向對象的三大特征

    Java面向對象的三大特征

    這篇文章主要給大家結合相關示例介紹了Java面向對象的三大特征:封裝、繼承、多態,非常的實用,有需要的小伙伴可以參考下。
    2015-06-06
  • java 與web服務器鏈接的實例

    java 與web服務器鏈接的實例

    這篇文章主要介紹了java 與web服務器鏈接的實例的相關資料,使用net.Socket類sock.getInetAddress()方法獲得與Web服務器連接,需要的朋友可以參考下
    2017-07-07
  • 解決IntelliJ IDEA 控制臺輸出中文亂碼問題(史上最簡單)

    解決IntelliJ IDEA 控制臺輸出中文亂碼問題(史上最簡單)

    這篇文章主要介紹了史上最簡單的IntelliJ IDEA 控制臺輸出中文亂碼問題的解決方法,非常不錯,具有一定的參考借鑒價值,需要的朋友參考下吧
    2018-05-05
  • Java編程中線程池的基本概念和使用

    Java編程中線程池的基本概念和使用

    這篇文章主要介紹了Java編程中線程池的基本概念和使用,多線程編程是使Java程序實現并發的一個重要手段,需要的朋友可以參考下
    2015-11-11

最新評論

pc蛋蛋幸运28app下载地址