Spring自學之路---AOP

加瓦一枚發表於2018-10-23

目錄

 

介紹

基於AspectJ的Spring AOP操作

XML方式

定義切點

案例擴充套件

註解方式


介紹

  AOP,即面向切面程式設計,簡單來說其思想指的是在不修改原有程式碼的情況下擴充套件新的功能。在傳統OOP思想下,我們擴充套件一個類的功能,可能採取的方式為縱向繼承,即定義父類,編寫新功能,通過繼承的形式在子類中使用父類的方法擴充套件功能。這種方式下,存在著類與類之間的耦合,多個類需要擴充套件多個新的功能,都需要新增新的程式碼到原有類中。
  而AOP思想則表示採用“在程式執行期間將某段程式碼,動態的切入到某個類的指定方法的指定位置”的方式,即我們不需要對原有類的功能進行修改,而是在程式執行過程中,將新增的功能加入都被擴充套件的型別的指定方法的指定位置。
AOP底層原理

  AOP,面向切面程式設計,底層使用動態代理方式實現。針對實現了介面的類的橫向擴充套件,採用的是JDK動態代理進行實現。在JDK動態代理中,為對原有類(實現了某一介面的類)進行橫向擴充套件,內部建立了實現了同一介面的類代理物件,具有與原有類物件相同的功能,且進行了增強。對於未實現介面的類,想實現橫向的擴充套件,則需要使用cglib動態代理實現,內部建立原有類的子類的代理物件,即在子類中呼叫父類的方法完成增強。
  詳細的分析還有待我深入的研究(20180320)!!!
核心概念

    連線點(Joinpoint):被攔截到的點。在Spring中只支援方法型別的連線點,因此這些點在Spring中指的是方法。簡單來說,我們可以認為是可以被增強功能的方法。
    切入點(Pointcut):對連線點進行攔截的定義。在Spring中通過表示式(後續將進行說明)形式來定義所匹配的類下的方法,以此定義切入點。
    通知/增強(Advice):所謂通知指的就是指攔截到連線點之後要執行的程式碼,通知分為前置通知(before 在方法之前執行)、後置(after-returning 在方法之後執行)、異常(after-throwing 在方法出現異常時執行)、最終(after 在後置之後執行,無論方法是否執行成功)、環繞(around 在方法之前和之後執行)通知五類。
    目標物件(Target):代理的類或是要被增強的類。
    織入(Weaving):將目標增強的過程,或者是是通知應用到目標的過程。
    代理(Proxy):一個類進行織入後將產生一個結果類,稱為代理類。
    切面(Aspect):是切入點和通知的結合。

基於AspectJ的Spring AOP操作


AOP操作準備

  (1)匯入jar包,除包括Spring基礎jar包外,還需要匯入AOP相關的jar包,此外為了方便測試還需引入junit相關的jar包,包括如下:

  Spring基礎jar包:
1. spring-beans
2. spring-context
3. spring-core
4. spring-expression
5. commons-logging-1.2.jar
6. log4j-1.2.17.jar
  Spring AOP相關的jar包:
1. aopalliance-1.0.jar
2. aspectjweaver-1.8.7.jar
3. spring-aop
4. spring-aspects
  junit相關的jar包:
1. junit-4.12.jar
2. hamcrest-core-1.3.jar

 

  (2)建立Spring核心配置檔案,並匯入AOP的約束。這裡我將該核心配置檔案命名為aop.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/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">

</beans>

 

XML方式

案例:在User類的add方法執行前後加入日誌記錄功能。
(1)建立需要被增強的類,User.java,如下:

package com.wm103.aop;

/**
 * Created by DreamBoy on 2018/3/18.
 */
public class User {
    public void add() {
        System.out.println("Running User Add Method.");
    }
}

   

(2)建立用於增強其他類的類,這裡以日誌類為例,即目的是為在User物件add方法呼叫前後,寫入日誌。LogHandler.java,如下:

package com.wm103.aop;

/**
 * Created by DreamBoy on 2018/3/18.
 */
public class LogHandler {
    public void before() {
        System.out.println("Start Write Log.");
    }

    public void after() {
        System.out.println("End Write Log.");
    }
}

(3)在Spring核心配置檔案(這裡為aop.xml)中配置,實現類的增強。如下:

<!-- 1. 配置物件 -->
<bean id="user" class="com.wm103.aop.User"></bean>
<bean id="logHandler" class="com.wm103.aop.LogHandler"></bean>

<!-- 2. AOP配置 -->
<aop:config>
    <!-- 2.2 配置切入點 -->
    <aop:pointcut id="pointcut" expression="execution(* com.wm103.aop.User.*(..))"/>

    <!-- 2.1 配置切面 -->
    <aop:aspect ref="logHandler">
        <!-- 2.3 針對切入點,配置通知/增強 -->
        <aop:before method="before" pointcut-ref="pointcut"/>
        <aop:after method="after" pointcut-ref="pointcut"/>
    </aop:aspect>
</aop:config>

 

(4)建立測試類,TestAop.java,如下:

package com.wm103.aop;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Created by DreamBoy on 2018/3/18.
 */
public class TestAop {
    @Test
    public void runUser() {
        ApplicationContext context = new ClassPathXmlApplicationContext("aop.xml");
        User user = (User) context.getBean("user");
        user.add();
    }
}

 

定義切點

使用表示式配置切入點:
  在上述例子中為實現AOP操作,對切入點進行定義,定義如下:

<aop:pointcut id="pointcut" expression="execution(* com.wm103.aop.User.*(..))"/>

     在expression屬性中,我們通過使用execution屬性對方法進行了定義,即規定哪些方法為切入點。該表示式的語法如下:

execution(<修飾符模式>?<返回型別模式><方法名模式>(<引數模式>)<異常模式>?)  除了返回型別模式、方法名模式和引數模式外,其它項都是可選的。

   舉例幾個主要使用:
1. execution(public * *(..)):匹配所有類的public方法;
2. execution(* com.wm103.dao.*(..)):匹配指定包(這裡指com.wm103.dao)下的所有類的方法,不包含子包;
3. execution(* com.wm103.dao..*(..)):匹配指定包以及其下子孫包下的所有類的方法,值得注意的是,“..”出現在類名中時,後面必須跟“*”,表示包、子孫包下的所有類;
4. execution(* com.wm103.dao.UserDaoImpl.*(..)):匹配指定類下的所有方法;
5. execution(* com.wm103.dao.UserDao+.*(..)):匹配實現指定介面(這裡指com.wm103.dao.UserDao介面)的所有類的方法;
6. execution(* save*(..)):匹配所有指定字首(這裡指的是save)開頭的方法;
7. execution(* *.*(..)):匹配所有類的所有方法。


案例擴充套件

案例:在上述案例中,對User類的add方法進行補充,增加記錄方法執行前的時間和執行後的時間以及花費的時間。
(1)建立用於增強其他類的類,這裡以時間類為例,即目的是為在User物件add方法呼叫前後,寫入執行時間。TimeHandler.java,如下:

package com.wm103.aop;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Created by DreamBoy on 2018/3/18.
 */
public class TimeHandler {
    private long startTime = 0;
    private long endTime = 0;

    public void before() {
        startTime = new Date().getTime();
        System.out.println("Start Time: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(startTime) + ".");
    }

    public void after() {
        endTime = new Date().getTime();
        System.out.println("End Time: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(endTime) + ".");
        long costTime = endTime - startTime;
        System.out.println("Total Cost Time: " + (costTime / 1000.0) + "s.");
    }
}

(2)對上述Spring核心配置檔案,進行進一步補充。如下:
配置物件

<bean id="timeHandler" class="com.wm103.aop.TimeHandler"></bean>

 

配置切面

<aop:aspect ref="timeHandler">
    <aop:before method="before" pointcut-ref="pointcut"/>
    <aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>

 

(3)執行測試類TestAop,執行結果如下:

Start Write Log.
Start Time: 2018-03-21 00:01:35.
Running User Add Method.
End Time: 2018-03-21 00:01:35.
Total Cost Time: 0.075s.
End Write Log.

 

  增強的順序預設按照xml中配置的順序執行。如果需要將TimeHandler增強先執行的話,那麼可以調整LogHandler和TimeHandler的順序。或者在aop:aspect標籤中使用order屬性(按從小到大的順序執行)。如下:

<!-- 2.1 配置切面 -->
<aop:aspect ref="logHandler" order="2">
    <!-- 2.3 針對切入點,配置通知/增強 -->
    <aop:before method="before" pointcut-ref="pointcut"/>
    <aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>

<aop:aspect ref="timeHandler" order="1">
    <aop:before method="before" pointcut-ref="pointcut"/>
    <aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>

  執行結果如下:

Start Time: 2018-03-21 00:08:29.
Start Write Log.
Running User Add Method.
End Write Log.
End Time: 2018-03-21 00:08:29.
Total Cost Time: 0.082s.
TestExp Run Method.

(4)環繞通知實現
  在User.java中新增del方法,如下:

public void del() {
    System.out.println("Running User Del Method.");
}

 

  為實現環繞通知,在LogHandler.java中新增如下方法:

public void around(ProceedingJoinPoint joinPoint) {
    try {
        before();
        joinPoint.proceed();
        after();
    } catch (Throwable throwable) {
        throwable.printStackTrace();
    }
}

  其中joinPoint.proceed();表示呼叫被增強類的方法。
  在Spring核心配置檔案中,單獨為User類的del方法定義新的切入點,如下:

<aop:pointcut id="pointcut3" expression="execution(* com.wm103.aop.User.del(..))"/>

    1

  配置切面,針對切入點實現環繞通知,如下:

<aop:aspect ref="logHandler">
    <aop:around method="around" pointcut-ref="pointcut3"/>
</aop:aspect>

 

  以上操作即簡單實現了環繞通知。我們可以在被增強的類的方法前後執行所需功能的程式程式碼,以達到增強類方法的目的。測試結果如下:

Start Write Log.
Running User Del Method.
End Write Log.

註解方式

案例:使用註解方式實現前置通知。如下:
(1)建立User.java,內容如下:

package com.wm103.aopanno;

/**
 * Created by DreamBoy on 2018/3/18.
 */
public class User {
    public void add() {
        System.out.println("Running User Add Method.");
    }
}

 

(2)建立LogHandler.java,這裡以日誌為例,如下:

package com.wm103.aopanno;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

/**
 * Created by DreamBoy on 2018/3/18.
 */
public class LogHandler {
    public void before() {
        System.out.println("Start Write Log.");
    }

    public void after() {
        System.out.println("End Write Log.");
    }
}

 

(3)定義Spring核心配置檔案,這裡命名為 aopanno.xml,並開啟AOP註解掃描,配置物件,內容如下:

<?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/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">

    <!-- 1. 開啟AOP註解掃描 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

    <!-- 2. 配置物件 -->
    <bean id="user" class="com.wm103.aopanno.User"></bean>
    <bean id="logHandler" class="com.wm103.aopanno.LogHandler"></bean>
</beans>

   

(4)開啟AOP註解掃描後,我們還需要在增強類上使用註解@Aspect,在增強方法上使用註解定義切入點,以及設定通知型別。修改LogHandler.java如下:

package com.wm103.aopanno;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

/**
 * Created by DreamBoy on 2018/3/18.
 */
@Aspect
public class LogHandler {
    @Before(value="execution(* com.wm103.aopanno.User.*(..))")
    public void before() {
        System.out.println("Start Write Log.");
    }

    @After(value="execution(* com.wm103.aopanno.User.*(..))")
    public void after() {
        System.out.println("End Write Log.");
    }
}   

(5)建立測試類TestAop.java進行測試,內容如下:

package com.wm103.aopanno;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Created by DreamBoy on 2018/3/18.
 */
public class TestAop {
    @Test
    public void runUser() {
        ApplicationContext context = new ClassPathXmlApplicationContext("aopanno.xml");
        User user = (User) context.getBean("user");
        user.add();
    }
}

(6)總結
  本案例採用註解的方式簡單的實現了類的增強。除了前置通知和最終通知,@AspectJ同樣也提供了對應的其他通知型別的註解形式,如下:

@Before 前置通知
@AfterReturning 後置通知
@Around 環繞通知
@AfterThrowing 異常通知
@After 最終通知(不管是否異常,該通知都會被執行)

 


--------------------- 
原文:https://blog.csdn.net/qq_15096707/article/details/79602676