【小家java】Spring AOP的多種使用方式以及神一樣的AspectJ-AOP使用介紹
相關閱讀
什麼時候AOP
AOP(Aspect-OrientedProgramming,面向方面程式設計),可以說是OOP(Object-Oriented Programing,面向物件程式設計)的補充和完善。
AOP技它利用一種稱為“橫切”的技術,剖解開封裝的物件內部,並將那些影響了多個類的公共行為封裝到一個可重用模組,並將其名為“Aspect”,即切面。所謂“方面”,簡單地說,就是將那些與業務無關,卻為業務模組所共同呼叫的邏輯或責任封裝起來,便於減少系統的重複程式碼,降低模組間的耦合度,並有利於未來的可操作性和可維護性。AOP代表的是一個橫向的關係,如果說“物件”是一個空心的圓柱體,其中封裝的是物件的屬性和行為;那麼面向方面程式設計的方法,就彷彿一把利刃,將這些空心圓柱體剖開,以獲得其內部的訊息。而剖開的切面,也就是所謂的“方面”了。然後它又以巧奪天功的妙手將這些剖開的切面復原,不留痕跡。
實現AOP的技術,主要分為兩大類:一是採用動態代理技術(典型代表為Spring AOP),利用擷取訊息的方式(典型代表為AspectJ-AOP),對該訊息進行裝飾,以取代原有物件行為的執行;二是採用靜態織入的方式,引入特定的語法建立“方面”,從而使得編譯器可以在編譯期間織入有關“方面”的程式碼。
相關概念
- 切面(Aspect):一個關注點的模組化,這個關注點實現可能另外橫切多個物件。事務管理是J2EE應用中一個很好的橫切關注點例子。切面用spring的 Advisor或攔截器實現。
- 連線點(Joinpoint): 程式執行過程中明確的點,如方法的呼叫或特定的異常被丟擲。
- 通知(Advice): 在特定的連線點,AOP框架執行的動作。各種型別的通知包括“around”、“before”和“throws”通知。通知型別將在下面討論。許多AOP框架包括Spring都是以攔截器做通知模型,維護一個“圍繞”連線點的攔截器鏈。Spring中定義了四個advice: BeforeAdvice, AfterAdvice, ThrowAdvice和DynamicIntroductionAdvice
- 切入點(Pointcut): 指定一個通知將被引發的一系列連線點的集合。AOP框架必須允許開發者指定切入點:例如,使用正則表示式。 Spring定義了Pointcut介面,用來組合MethodMatcher和ClassFilter,可以通過名字很清楚的理解, MethodMatcher是用來檢查目標類的方法是否可以被應用此通知,而ClassFilter是用來檢查Pointcut是否應該應用到目標類上
- 引入(Introduction): 新增方法或欄位到被通知的類。 Spring允許引入新的介面到任何被通知的物件。例如,你可以使用一個引入使任何物件實現 IsModified介面,來簡化快取。Spring中要使用Introduction, 可有通過DelegatingIntroductionInterceptor來實現通知,通過DefaultIntroductionAdvisor來配置Advice和代理類要實現的介面(使用較少)
- 目標物件(Target Object): 包含連線點的物件。也被稱作被通知或被代理物件。POJO
- AOP代理(AOP Proxy): AOP框架建立的物件,包含通知。 在Spring中,AOP代理可以是JDK動態代理或者CGLIB代理。
- 織入(Weaving): 組裝方面來建立一個被通知物件。這可以在編譯時完成(例如使用AspectJ編譯器),也可以在執行時完成。Spring和其他純Java AOP框架一樣,在執行時完成織入。
AOP概念的通俗理解
1.通知(Advice): 通知定義了切面是什麼以及何時使用。描述了切面要完成的工作和何時需要執行這個工作。 2.連線點(Joinpoint): 程式能夠應用通知的一個“時機”,這些“時機”就是連線點,例如方法被呼叫時、異常被丟擲時等等。 3.切入點(Pointcut) :通知定義了切面要發生的“故事”和時間,那麼切入點就定義了“故事”發生的地點,例如某個類或方法的名稱,Spring中允許我們方便的用正則表示式來指定 4.切面(Aspect) :通知和切入點共同組成了切面:時間、地點和要發生的“故事” 5.引入(Introduction) :引入允許我們向現有的類新增新的方法和屬性(Spring提供了一個方法注入的功能) 6.目標(Target) :即被通知的物件,如果沒有AOP,那麼它的邏輯將要交叉別的事務邏輯,有了AOP之後它可以只關注自己要做的事(AOP讓他做愛做的事) 7.代理(proxy) :應用通知的物件,詳細內容參見設計模式裡面的代理模式 8.織入(Weaving) :把切面應用到目標物件來建立新的代理物件的過程,織入一般發生在如下幾個時機: ---- (1)編譯時:當一個類檔案被編譯時進行織入,這需要特殊的編譯器才可以做的到,例如AspectJ的織入編譯器 ---- (2)類載入時:使用特殊的ClassLoader在目標類被載入到程式之前增強類的位元組程式碼 ----(3)執行時:切面在執行的某個時刻被織入,SpringAOP就是以這種方式織入切面的,原理應該是使用了JDK的動態代理技術
Spring AOP的三種實現方式(基於Spring Boot)
一、基於XML配置的Spring AOP
現在都是spring boot的時代了,因此基於xml配置的例子,本文不做介紹了,有需要的可以自己去找其餘博文閱讀
二、基於ProxyFactoryBean,編碼的方式來實現
導包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
有如下包即可正常工作了
為何Spring自己實現了AOP,還需要匯入org.aspectj:aspectjweaver的包呢?
官網解釋的原因如下:
- 原因一:spring確實有自己的AOP。功能已經基本夠用了,除非你的要在介面上動態代理或者方法攔截精確到getter和setter。這些都是寫奇葩的需求Spring做不到,但一般不使用。
- 原因二:1、如果使用xml方式,不需要任何額外的jar包。2、如果使用@Aspect的註解方式。你就可以在類上直接一個@Aspect就搞定,不用費事在xml裡配了。但是這需要額外的jar包( aspectjweaver.jar)。因為spring直接使用AspectJ的註解功能,注意只是使用了它 的註解功能而已。並不是核心功能 !!!
注意到文件上還有一句很有意思的話:文件說到 是選擇spring AOP還是使用full aspectJ?什麼是full aspectJ?如果你使用"full aspectJ"。就是說你可以實現基於介面的動態代理,等等強大的功能。而不僅僅是aspectj的 注-解-功-能 !!!
如果用full AspectJ。比如說Load-Time Weaving的方式 還 需要額外的jar包 spring-instrument.jar。。。現在明白了吧~~~ 具體詳情,後面在講述AspectJ裡可以看見~~
基本類如下:
// A類:
@Service
public class AServiceImpl implements AService {
@Override
public void sayHelloA() {
System.out.println("hello A");
}
}
// B類:
@Service
public class BServiceImpl implements BService {
@Override
public void sayHelloB() {
System.out.println("hello B");
}
}
// C類:
@Service
public class CServiceImpl implements CService {
@Override
public void sayHelloC() {
System.out.println("hello C");
}
}
通過實現介面的方式編寫的通知類
/**
* 在方法之前、之後 列印輸出日誌
*
* @author [email protected]
* @description
* @date 2018-10-29 17:42
*/
@Component //通知元件交給容器管理
public class LogAdvice implements MethodBeforeAdvice, AfterReturningAdvice, MethodInterceptor {
//MethodBeforeAdvice的方法
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("MethodBeforeAdvice...before...");
}
//AfterReturningAdvice的方法
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("AfterReturningAdvice...afterReturning...");
}
//MethodInterceptor的方法
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("MethodInterceptor...invoke...start");
Object proceed = invocation.proceed();
System.out.println("MethodInterceptor...invoke...end");
return proceed;
}
}
實現介面 MethodBeforeAdvice該攔截器會在呼叫方法前執行 實現介面 AfterReturningAdvice該攔截器會在呼叫方法後執行 實現介面 MethodInterceptor該攔截器會在呼叫方法前後都執行,實現環繞效果
然後就是配置了,其中最重要的類為ProxyFactoryBean、BeanNameAutoProxyCreator、AspectJExpressionPointcutAdvisor等等代理類,能達到強大的效果。這種一般都是spring時代基於xml的書寫方式,因此這裡不做詳細講解,SpringBoot時代,建議使用優雅的註解的風格編寫,但本文提供一個參考博文: Spring AOP之ProxyFactoryBean與BeanNameAutoProxyCreator
三、基於註解方式@AspectJ實現AOP
PS:其實springboot此配置是預設開啟的,所以根本可以不用管了,在Springboot中使用過註解配置方式的人會問是否需要在程式主類中增加@EnableAspectJAutoProxy來啟用,實際並不需要。看下面關於AOP的預設配置屬性,其中spring.aop.auto屬性預設是開啟的,也就是說只要引入了AOP依賴後,其實預設已經增加了@EnableAspectJAutoProxy。 截圖看看Boot的一些預設配置:
不過後面會有點奇怪的問題,springboot中,不管這個項是否設定為true或者false,都不會跟以前spring專案中,如果沒有設定為true,當代理類沒有繼承介面,啟動專案的時候會報錯。而springboot專案中,會自動轉換成使用CGLIB進行動態代理,其中原理是怎麼實現,就沒有去看底層程式碼了,估計底層進行了改造吧! 切面:
/**
* @author [email protected]
* @description
* @date 2018-10-30 11:32
*/
@Component //這個元件一定得加入到容器才行
@Aspect
public class SpringLogAspect {
//定義一個切入點:指定哪些方法可以被切入(如果是別的類需要使用 請用該方法的全類名)
@Pointcut("execution(* com.fsx.run.service..*.*(..))")
public void pointCut() {
}
@Before("pointCut()")
public void doBefore(JoinPoint joinPoint) {
System.out.println("AOP Before Advice...");
}
@After("pointCut()")
public void doAfter(JoinPoint joinPoint) {
System.out.println("AOP After Advice...");
}
@AfterReturning(pointcut = "pointCut()", returning = "returnVal")
public void afterReturn(JoinPoint joinPoint, Object returnVal) {
System.out.println("AOP AfterReturning Advice:" + returnVal);
}
@AfterThrowing(pointcut = "pointCut()", throwing = "error")
public void afterThrowing(JoinPoint joinPoint, Throwable error) {
System.out.println("AOP AfterThrowing Advice..." + error);
System.out.println("AfterThrowing...");
}
// 環繞通知:此處有一個坑,當AfterReturning和Around共存時,AfterReturning是獲取不到返回值的
//@Around("pointCut()")
//public void around(ProceedingJoinPoint pjp) {
// System.out.println("AOP Aronud before...");
// try {
// pjp.proceed();
// } catch (Throwable e) {
// e.printStackTrace();
// }
// System.out.println("AOP Aronud after...");
//}
}
idea很智慧:如果切中了,會有這個小圖示的 controller層這麼測試:
@Autowired
AService aService;
@Autowired
BService bService;
@Override
public Object testDemo(String str) {
aService.sayHelloA();
bService.sayHelloB();
return str + "succuss~";
}
訪問,控制檯列印結果如下:
AOP Before Advice...
hello A
AOP After Advice...
AOP AfterReturning Advice:AServiceImpl
AOP Before Advice...
hello B
AOP After Advice...
AOP AfterReturning Advice:BServiceImpl
此處有一個坑,當AfterReturning和Around共存時,AfterReturning是獲取不到返回值的。當然,如果你的方法本來就沒有返回值,那肯定也是null咯