Kotlin的Spring之旅(二):AOP(面向切面程式設計)
阿新 • • 發佈:2019-01-01
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)
}
}