Kotlin的Spring之旅(二):AOP(面向切面編程)

AOP(面向切面編程)

AOP是OOP(面向對象編程)的延續,但是它和麪向對象的縱向編程不同,它是一個橫向的切面式的編程。可以理解爲oop就是一根柱子,如果需要就繼續往上加長,而aop則是在需要的地方把柱子切開,在中間加上一層,再把柱子完美的粘合起來。

用物理上的話來說,aop就是給這個編程世界加上了一個維度,二維到三維的差別。很明顯aop要靈活得多

AOP主要實現的目的是針對業務處理過程中的切面進行提取,它所面對的是處理過程中的某個步驟或階段,以獲得邏輯過程中各部分之間低耦合性的隔離效果。

AOP核心概念

  • Joinpoint(連接點):指那些被攔截到的點(spring中這些點指的是方法)
  • Pointcut(切入點):指我們要對哪些joinpoint進行攔截的定義
  • Advice(通知/增強):攔截到joinpoint之後多要做的事情就是通知(通知分爲前置通知,後置通知,異常通知,最終通知,環繞通知)
  • Introduction(引介):引介是一種特殊的通知。在不改變代碼的前提下,introduction可以在運行期間爲類動態日案件一些方法或Field
  • Target(目標對象):代理的目標對象(需要增強的類)
  • Weaving(織入):把增強應用到目標的過程(advice應用到target的過程)
  • Proxy(代理):一個類被AOP織入增強後,就會產生一個結果代理類
  • Aspect(切面):是切入點和通知(引介)的結合

    看不懂吧<( ̄︶ ̄)>,因爲我之前也沒看懂,沒事,我們寫幾個例子看看就能懂了。如果你沒學過還能看懂,那我也只能膜拜大佬了

概念說完,下面開始進入正式環節

第一步 添加依賴

想使用AOP光憑之前的那些還是不夠的,所以我們還需要添加一些依賴

compile "org.springframework:spring-aspects:4.3.9.RELEASE"
compile "org.springframework:spring-aop:4.3.9.RELEASE"
compile "aspectj:aspectjweaver:1.5.4"
compile "aopalliance:aopalliance:1.0"

這裏面的aspectj可以看到,並不是spring的一部分,但是自從spring2之後,官方就添加了對aspectj的支持,而且現在spring官方是推薦使用aspectj來開發aop,我們自然要跟隨官方的大旗走了

aspectj實現aop有兩種方式,而且還是老兩種

  • xml配置
  • 註解實現

我們來看這兩種,和之前一樣,我們常用的肯定還是能少寫代碼的註解方式,xml配置的方式看看就可以了,但是至少也要能看懂,不然別人寫了你看不懂就尷尬了

1. xml配置

首先在我們的xml文件中加入約束

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context.xsd
                        http://www.springframework.org/schema/aop
                        http://www.springframework.org/schema/aop/spring-aop.xsd">

可以看到我們又加上了aop的約束

我們還是繼續用我們的User類不過這次一個類不夠用,我們需要再來一個類就叫Advice吧,而且我們還需要在類中加點方法

@Bean
data class User(var name: String, var age: Int)
{
    fun add()
    {
        println("user add")
    }
}

@Bean
data class Advice(var content: String)
{
    fun before()
    {
        println("前置通知")
    }
}

然後就是配置xml了,我們通過xml來配置切入點(pointcut),這裏我們需要用到一個函數

execution(<訪問修飾符>?<返回類型><方法名>(<參數>)<異常>)

來點例子看一下:

  • 匹配所有public的方法:execution(public * *(..))
  • 匹配包下所有類的方法:execution(* com.kotlin.*(..)) (一個點表示不包含子包)
  • execution(* com.kotlin.. *(..)) (兩個點表示包含子包)
  • 匹配實現特定接口的所有類的方法:execution(* com.kotlin.xxinterface+.*(..))
  • 匹配所有add開頭的方法:execution(* add*(..))
  • 匹配所有方法:execution(* *.*(..))

這樣大概清楚了吧,下面我們我們來寫一個前置通知增強User中的add方法

   <!--1.配置對象-->
    <bean id="user" class="com.kotlin.Bean.User"></bean>
    <bean id="advice" class="com.kotlin.Bean.Advice"></bean>

    <!--2.配置aop操作-->
    <aop:config>
        <!--2.1配置切入點 因爲User中只有一個方法,就直接增強User中的所有方法了-->
        <aop:pointcut id="pointcut" expression="execution(* com.kotlin.Bean.User.*(..))"/>

        <!--2.2配置切面 將增強用到方法上-->
        <aop:aspect ref="advice">
            <!--選擇用來增強的方法和需要增強的切入點-->
            <aop:before method="before" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>

然後來到我們的測試類,運行下看看

class main
{
    @Test
    fun test()
    {
        //加載Spring配置文件,創建對象
        val context = FileSystemXmlApplicationContext("src/main/webapp/WEB-INF/applicationContext.xml")

        val user = context.getBean("user") as User
        user.add()
    }
}

這裏寫圖片描述

結果可以看到,before方法已經添加到add方法中了

下面我就直接演示下其他幾種的用法了

@Bean
data class User(var name: String, var age: Int)
{
    fun add(): String
    {
        println("user add")
        return "你好"
    }
}

@Bean
data class Advice(var content: String)
{
    fun before()
    {
        println("前置通知")
    }

    //後置通知需要傳入一個參數,這個參數就是需要增強方法的返回值,沒有可以不寫
    fun afterResult(result: Any)
    {
        println("後置通知  "+result)
    }

    //最終通知無論該方法有沒有出異常有沒有返回值,最終都會被執行
    fun after()
    {
        println("最終通知")
    }

    /* 環繞通知需要一個ProceedingJoinPoint參數,這相當於需要增強的函數的方法體,需要的調用它的proceed方法執行,如果該函數有返回值,那麼環繞通知也需要返回一個proceed方法的返回值 */
    fun around(pro: ProceedingJoinPoint): Any
    {
        //方法之前
        println("環繞通知 方法之前")

        //被增強的方法
        val any = pro.proceed()

        //方法之後
        println("環繞通知 方法之後")

        return any
    }

    //異常通知需要一個異常參數,當出現異常時該方法將會被調用
    fun exception(ex : Exception)
    {
        println("異常通知 "+ex)
    }
}
<!--1.配置對象-->
    <bean id="user" class="com.kotlin.Bean.User"></bean>
    <bean id="advice" class="com.kotlin.Bean.Advice"></bean>

    <!--2.配置aop操作-->
    <aop:config>
        <!--2.1配置切入點-->
        <aop:pointcut id="pointcut" expression="execution(* com.kotlin.Bean.User.*(..))"/>

        <!--2.2配置切面 將增強用到方法上-->
        <aop:aspect ref="advice">
            <!--選擇用來增強的方法和需要增強的切入點-->
            <aop:before method="before" pointcut-ref="pointcut"/>
            <!--後置通知需要配置它的參數-->
            <aop:after-returning method="afterResult" pointcut-ref="pointcut" returning="result"/>
            <aop:after method="after" pointcut-ref="pointcut" />
            <aop:around method="around" pointcut-ref="pointcut" />
            <!--異常通知也要配置它的異常參數-->
            <aop:after-throwing method="exception" pointcut-ref="pointcut" throwing="ex"/>

        </aop:aspect>
    </aop:config>

然後我們來看下結果

這裏寫圖片描述

接着,我們手動給他製造一個異常,就用4/0吧

這裏寫圖片描述

可以看到,後置通知和後環繞通知沒有了,取而代之的是除零異常,這時異常通知出現了,這也說明了後置通知只有在沒有異常時候纔會執行,異常通知只會在有異常時候執行

這也得出了這樣的結論

try
{
    //  前置通知
    //  環繞通知(前)

    //  目標方法

    //  環繞通知(後)
    //  後置通知(也有人稱爲返回通知)
}
catche(Exception e)
{
    //  異常通知
}
finally
{
    //  最終通知
}

2.註解配置

註解配置很簡單,直接把內容寫在方法的頭上就可以了,我把代碼給出,大家一看就知道了

首先在xml中開啓自動掃描

<!--開啓aop自動掃描代理-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

然後在各方法上寫上註解,別忘了類上面的註解

@Bean
@Component(value = "user")
data class User(var name: String, var age: Int)
{
    fun add(): String
    {
        println("user add")
//        var s = 4 / 0
        return "你好"
    }
}

@Aspect
@Bean
@Component(value = "advice")
data class Advice(var content: String)
{
    @Before(value = "execution(* com.kotlin.Bean.User.*(..))")
    fun before()
    {
        println("前置通知")
    }

    @AfterReturning(value = "execution(* com.kotlin.Bean.User.*(..))", returning = "result")
    fun afterResult(result: Any)
    {
        println("後置通知  " + result)
    }

    @After(value = "execution(* com.kotlin.Bean.User.*(..))")
    fun after()
    {
        println("最終通知")
    }

    @Around(value = "execution(* com.kotlin.Bean.User.*(..))")
    fun around(pro: ProceedingJoinPoint): Any
    {
        //方法之前
        println("環繞通知 方法之前")

        //被增強的方法
        val any = pro.proceed()

        //方法之後
        println("環繞通知 方法之後")

        return any
    }

    @AfterThrowing(value = "execution(* com.kotlin.Bean.User.*(..))", throwing = "ex")
    fun exception(ex: Exception)
    {
        println("異常通知 " + ex)
    }
}

別忘了開啓IOC的註解掃描

這裏寫圖片描述

結果自然毫無疑問

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章