Spring AOP 基於AspectJ

簡介

AspectJ是一個基於Java語言的AOP框架,Spring2.0之後新增了對AspectJ切點表達式支持。由於Spring1.0的時候Aspectj還未出現;java

AspectJ1.5中新增了對註解的支持,容許直接在Bean類中定義切面。新版本的Spring框架建
議咱們都使用AspectJ方式來開發AOP,並提供了很是靈活且強大的切點表達式 ;spring

固然不管使用Spring本身的AOP仍是AspectJ相關的概念都是相同的;數據庫

註解配置

依賴導入:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.2.2.RELEASE</version>
</dependency>

通知類型

@AspectJ提供的通知類型:express

  1. @Before 前置通知 在原始方法執行前執行app

  2. @AfterReturning 後置通知 在原始方法執行前執行框架

  3. @Around 環繞通知 完全攔截原始方法的執行,執行先後均可以增長邏輯,也能夠不執行原始方法函數

  4. @AfterThrowing拋出通知,執行原始方法出現異常時執行spa

  5. @After 最終final通知,無論是否異常,原始方法調用後都會執行代理

  6. @DeclareParents 引介通知,至關於IntroductionInterceptor (瞭解便可)code

定義切點

經過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(..))
*/

切點命名

假設有多個通知應用在同一個切點上時,咱們須要重複編寫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;

相關文章
相關標籤/搜索