1. 程式人生 > >【小家java】Spring AOP的多種使用方式以及神一樣的AspectJ-AOP使用介紹

【小家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 AOP的預設配置

不過後面會有點奇怪的問題,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咯