基本概念java
就是你想要的功能,也就是咱們常說的安全、事物、日誌等。先定義好這些,而後再想用的地方用一下。包含Aspect的一段處理代碼web
注意:其實這些功能(通知)並非咱們業務邏輯所必須的,只是爲了安全,輸出信息,或者其餘的緣由,總之是爲了方便咱們對項目維護而增長的操做,通常咱們會把這些功能封裝成相關的方法,可是咱們又不想這些功能直接入侵咱們的正常業務代碼,由於這樣會增長關注度而且污染咱們的業務邏輯,因此咱們就用切面的思想來很好解決這個問題spring
就是spring容許你加 通知(Advice)的地方,那可就真多了,基本每一個方法的前、後(二者都有也行),或拋出異常時均可以是鏈接點,spring通常只支持方法鏈接點,除非引入其餘的aop框架才能夠實現更細粒度的鏈接點。其餘如AspectJ還可讓你在構造器或屬性注入時都行,不過那不是我們關注的,只要記住,和方法有關的前先後後都是鏈接點express
在上面說的鏈接點的基礎上,來定義切入點。例如:你的一個類裏,有15個方法,那就有至少十幾個鏈接點了對吧,可是你並不想在全部方法附近都使用通知(使用叫織入,下面再說),你只是想讓其中幾個,在調用這幾個方法以前、以後或者拋出異常時乾點什麼,那麼就用切入點來定義這幾個方法,讓切點來篩選鏈接點,選中那幾個你想要的方法安全
注意:切入點定義的通常是你的業務中的某些方法(也有多是某些特殊地方,在「特例」下有個例子切點切的就是方法,而是自定義的註解),就是供切面實際切入的地方,也就是須要執行通知的地方框架
用來切插業務方法的類。fetch
切面是通知和切入點的結合。如今發現了吧,沒鏈接點什麼事,連接點就是爲了讓你好理解切點搞出來的,明白這個概念就好了。通知說明了幹什麼和何時幹(何時經過方法名中的befor,after,around等就能知道),二切入點說明了在哪幹(指定究竟是哪一個方法),這就是一個完整的切面定義spa
引入中所提到的目標類,也就是要被通知的對象,也就是真正的業務邏輯,他能夠在絕不知情的狀況下,被我們織入切面,而本身專一於業務自己的邏輯。代理
怎麼實現整套AOP機制的,都是經過代理,這個一下子給細說日誌
把切面應用到目標對象來建立新的代理對象的過程。有三種方式,spring採用的是運行時,爲何是運行時,在上一文《Spring AOP開發漫談之初探AOP及AspectJ的用法》中第二個標提到
項目原始的Java組件。
由AOP框架生成java對象。
代理方法= advice + 目標對象的方法。
xml方式配置詳解
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
<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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- 定義普通的Bean實例 -->
<
bean
id
=
"adviceTest"
class
=
"com.abc.advice.AdviceTest"
/>
<
aop:config
>
<!-- 將容器中的adviceTest轉換成切面Bean -->
<!-- 注意這裏可使用order屬性爲Aspect指定優先級 -->
<
aop:aspect
id
=
"firstAspect"
ref
=
"adviceTest"
order
=
"2"
>
<!-- @Before切點 -->
<
aop:before
pointcut
=
"execution(* com.abc.service.*.*(..))"
method
=
"permissionCheck" arg-names="name,age"
/>
<!-- @After切點 -->
<
aop:after
pointcut
=
"execution(* com.abc.service.*.*(..))"
method
=
"releaseResource"
/>
<!-- @AfterReturning切點 -->
<
aop:after-returning
pointcut
=
"execution(* com.abc.service.*.*(..))"
method
=
"log" returning="discussion" arg-names="discussion"
/>
<!-- @AfterThrowing切點 -->
<
aop:after-throwing
pointcut
=
"execution(* com.abc.service.*.*(..))"
method
=
"handleException"
/>
<!-- @Around切點(多個切點提示符使用and、or或者not鏈接) -->
<
aop:around
pointcut
=
"execution(* com.abc.service.*.*(..)) and args(name,time,..)"
method
=
"process"
/>
<!-切入點第二種寫法開始->
<!-單參數傳參->
<aop:pointcut id="log4add" expression="execution(* com.tfedu.discuss.web.TeacherDiscussionController.delete(com.tfedu.discuss.abutment.TestAddLog)) and args(testAddLog)" />
<aop:after method="addLog" pointcut-ref="log4add" arg-names="testAddLog"/>
<!-切入點第二種寫法結束->
<!-切點爲註解時,也就是將切點切到註解上 開始->
</
aop:aspect
>
</
aop:config
>
Spring中的事務是經過aop來實現的,當咱們本身寫aop攔截的時候,會遇到跟spring的事務aop執行的前後順序問題,好比說動態切換數據源的問題,若是事務在前,數據源切換在後,會致使數據源切換失效,因此就用到了Order(排序)這個關鍵字.咱們能夠經過在@AspectJ的方法中實現org.springframework.core.Ordered 這個接口來定義order的順序,order 的值越小,說明越先被執行
特例(某註解爲切點時)
通常咱們認爲切點的位置都是某個業務方法以前,以後或者先後都有,可是有些狀況下咱們也能夠將切點切到某個方法的註解上,在解決某些問題時很方便,下面舉例說明此種用法:
配置文件中內容
<bean id="abutmentAspect" class="com.tfedu.discuss.abutment.aop.LogAspect"></bean>
<aop:config>
<aop:aspect id="" ref="abutmentAspect">
<aop:pointcut id="addLog" expression="@annotation(com.tfedu.discuss.annotation.BehaviorLog)" />
<aop:after method="behaviorLog4Review" pointcut-ref="addLog"/>
</aop:aspect>
</aop:config>
註解類
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD })
@Documented
public @interface BehaviorLog {
OperTypeEnum value();//這裏面定義的方法要在註解上用到,這裏的方法名就是註解上的等號左邊的值
String fiterName();
}
注意:裏面的方法能夠根據具體須要來定義
註解的使用
在業務方法上加上:@BehaviorLog(value = OperTypeEnum.REVIEW, fiterName = "historyDraftId")
通知方法代碼
public void behaviorLog4Review(JoinPoint joinPoint) { Signature signature = joinPoint.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method method = methodSignature.getMethod(); String fiterName = method.getAnnotation(BehaviorLog.class).fiterName();//這裏獲取的是註解中定義的各類方法的值,例如這裏獲取的註解上的fiterName等號後面的的值 Object[] args = joinPoint.getArgs();//這裏獲取的是方法裏的參數不是註解上的值,這個切記 Long historyDraftId = getParam4Common(args, fiterName); HistoryDraftEntity historyDraftEntity = historyDraftBaseService.get(historyDraftId); if (Util.isEmpty(historyDraftEntity)) { ExceptionUtil.bEx("批閱中添加行爲日誌記錄是,歷史稿對象不能爲空", historyDraftEntity); } addLog(historyDraftId, LogTypeEnum.WRITING.key(), historyDraftEntity.getTitle(), fetchUser.getCurrentUserId(), OperTypeEnum.REVIEW.key()); } private Long getParam4Common(Object[] args, String fiterName) { Optional<Object> obj = Arrays.stream(args).findFirst(); if (!obj.isPresent()) { return PARAM_ID; } return ConvertUtil.obj2long(BeanUtil.get((obj.get()), fiterName)); }