天天看點

Spring 實踐 -AOP Spring 實踐

标簽: Java與設計模式

AOP(Aspect Oriented Programing)面向切面程式設計采用橫向抽取機制,以取代傳統的縱向繼承體系的重複性代碼(如性能監控/事務管理/安全檢查/緩存實作等).

橫向抽取代碼複用: 基于代理技術,在不修改原來代碼的前提下,對原有方法進行增強.

1.2開始, Spring開始支援AOP技術(Spring AOP)

Spring AOP使用純Java實作,不需要專門的編譯過程和類加載器,在運作期通過代理方式向目标類織入增強代碼.

2.0之後, 為了簡化AOP開發, Spring開始支援AspectJ(一個基于Java的AOP架構)架構.

術語

中文

描述

Joinpoint

連接配接點

指那些被攔截到的點.在Spring中,這些點指方法(因為Spring隻支援方法類型的連接配接點).

Pointcut

切入點

指需要(配置)被增強的Joinpoint.

Advice

通知/增強

指攔截到Joinpoint後要做的操作.通知分為前置通知/後置通知/異常通知/最終通知/環繞通知等.

Aspect

切面

切入點和通知的結合.

Target

目标對象

需要被代理(增強)的對象.

Proxy

代理對象

目标對象被AOP 織入 增強/通知後,産生的對象.

Weaving

織入

指把增強/通知應用到目标對象來建立代理對象的<code>過程</code>(Spring采用動态代理織入,AspectJ采用編譯期織入和類裝載期織入).

Introduction

引介

一種特殊通知,在不修改類代碼的前提下,可以在運作期為類動态地添加一些Method/Field(不常用).

cglib(Code Generation Library)是一個開源/高性能/高品質的Code生成類庫,可以在運作期動态擴充Java類與實作Java接口.

UserDAO(并沒有實作接口)

CGLibProxyFactory

Spring AOP的底層通過JDK/cglib動态代理為目标對象進行橫向織入:

1) 若目标對象實作了接口,則Spring使用JDK的<code>java.lang.reflect.Proxy</code>代理.

2) 若目标對象沒有實作接口,則Spring使用cglib庫生成目标對象的子類.

Spring隻支援方法連接配接點,不提供屬性連接配接.

标記為<code>final</code>的方法不能被代理,因為無法進行覆寫.

程式應優先對針對接口代理,這樣便于程式解耦/維護.

AOP聯盟為通知<code>Advice</code>定義了<code>org.aopalliance.aop.Advice</code>接口, Spring在<code>Advice</code>的基礎上,根據通知在目标方法的連接配接點位置,擴充為以下五類:

通知

接口

前置通知

<code>MethodBeforeAdvice</code>

在目标方法執行前實施增強

後置通知

<code>AfterReturningAdvice</code>

…執行後實施增強

環繞通知

<code>MethodInterceptor</code>

..執行前後實施增強

異常抛出通知

<code>ThrowsAdvice</code>

…抛出異常後實施增強

引介通知

<code>IntroductionInterceptor</code>

在目标類中添加新的方法和屬性(少用)

添加Spring的AOP依賴

使用Spring的AOP和AspectJ需要在pom.xml中添加如下依賴:

定義Target

定義Advice

配置代理

Spring最原始的AOP支援, 手動指定目标對象與通知(沒有使用AOP名稱空間).

Client

這種方式的缺陷在于每個<code>Target</code>都必須手動指定<code>ProxyFactoryBean</code>對其代理(不能批量指定),而且這種方式會在Spring容器中存在兩份Target對象(代理前/代理後),浪費資源,且容易出錯(比如沒有指定<code>@Qualifier</code>).

通過AspectJ引入Pointcut切點定義

Target/Advice同前

定義切面表達式

通過execution函數定義切點表達式(定義切點的方法切入) <code>execution(&lt;通路修飾符&gt; &lt;傳回類型&gt;&lt;方法名&gt;(&lt;參數&gt;)&lt;異常&gt;)</code> 如: 1) <code>execution(public * *(..))</code> # 比對所有<code>public</code>方法. 2) <code>execution(* com.fq.dao.*(..))</code> # 比對指定包下所有類方法(不包含子包) 3) <code>execution(* com.fq.dao..*(..))</code> # 比對指定包下所有類方法(包含子包) 4) <code>execution(* com.fq.service.impl.OrderServiceImple.*(..))</code> # 比對指定類所有方法 5) <code>execution(* com.fq.service.OrderService+.*(..))</code> # 比對實作特定接口所有類方法 6) <code>execution(* save*(..))</code> # 比對所有save開頭的方法

Client同前

AspectJ是一個基于Java的AOP架構,提供了強大的AOP功能,其他很多AOP架構都借鑒或采納了AspectJ的一些思想,Spring2.0以後增加了對AspectJ切點表達式支援(如上),并在Spring3.0之後與AspectJ進行了很好的內建.

在Java領域,AspectJ中的很多文法結構基本上已成為AOP領域的标準, 他定義了如下幾類通知類型:

<code>@Before</code>

相當于<code>BeforeAdvice</code>

<code>@AfterReturning</code>

相當于<code>AfterReturningAdvice</code>

<code>@Around</code>

相當于<code>MethodInterceptor</code>

抛出通知

<code>@AfterThrowing</code>

相當于<code>ThrowAdvice</code>

<code>@DeclareParents</code>

相當于<code>IntroductionInterceptor</code>

最終final通知

<code>@After</code>

不管是否異常,該通知都會執行

新版本Spring,建議使用AspectJ方式開發以簡化AOP配置.

使用AspectJ編寫Advice無需實作任何接口,而且可以将多個通知寫入一個切面類.

定義通知

裝配

前置通知小結

前置通知會保證在目标方法執行前執行;

前置通知預設不能阻止目标方法執行(但如果通知抛出異常,則目标方法無法執行);

可以通過<code>JoinPoint</code>參數獲得目前攔截對象和方法等資訊.

後置通知可以獲得方法傳回值,但在配置檔案定義傳回值參數名必須與後置通知方法參數名一緻(如<code>result</code>).
環繞通知可以實作任何通知的效果, 甚至可以阻止目标方法的執行.
<code>throwing</code>屬性指定異常對象名, 該名稱應和方法定義參數名一緻.
無論目标方法是否出現異常,該通知都會執行(類似<code>finally</code>代碼塊, 應用場景為釋放資源).

<code>@AspectJ</code>是AspectJ 1.5新增功能,可以通過JDK注解技術,直接在Bean類中定義切面.

AspectJ預定義的注解有:<code>@Before</code>/<code>@AfterReturning</code>/<code>@Around</code>/<code>@AfterThrowing</code>/<code>@DeclareParents</code>/<code>@After</code>.描述同前.

使用AspectJ注解AOP需要在applicationContext.xml檔案中開啟注解自動代理功能:

<code>OrderService</code>/<code>Client</code>同前

如果不調用<code>ProceedingJoinPoint</code>的<code>proceed</code>方法,那麼目标方法就不執行了.

對于重複的切點,可以使用<code>@Pointcut</code>進行定義, 然後在通知注解内引用.

定義切點方法

無參/無傳回值/方法名為切點名:

引用切點

在Advice上像調用方法一樣引用切點:

1) 如果切點與切面在同一個類内, 可省去類名字首; 2) 當需要通知多個切點時,可以使用<code>||</code>/<code>&amp;&amp;</code>進行連接配接.

權限控制(少用)

少用

權限控制/性能監控/緩存實作/事務管理

異常通知

發生異常後,記錄錯誤日志

最終通知

釋放資源