SpringBoot強化篇(八)-- Spring AOP

Spring AOP簡介

AOP(Aspect Orient Programming)是一種設計思想,是軟件設計領域中的面向切面編程,它是面向對象編程(OOP)的一種補充和完善。它以經過預編譯方式和運行期動態代理方式,實如今不修改源代碼的狀況下給程序動態統一添加額外功能的一種技術。
image8.png
AOP與OOP字面意思相近,但其實二者徹底是面向不一樣領域的設計思想。實際項目中咱們一般將面向對象理解爲一個靜態過程(例如一個系統有多少個模塊,一個模塊有哪些對象,對象有哪些屬性),面向切面的運行期代理方式,理解爲一個動態過程,能夠在對象運行時動態織入一些擴展功能或控制對象執行。java

AOP 應用場景分析?

實際項目中一般會將系統分爲兩大部分,一部分是核心業務,一部分是非核業務。在編程實現時咱們首先要完成的是核心業務的實現,非核心業務通常是經過特定方式切入到系統中,這種特定方式通常就是藉助AOP進行實現。算法

AOP就是要基於OCP(開閉原則),在不改變原有系統核心業務代碼的基礎上動態添加一些擴展功能並能夠"控制"對象的執行。例如AOP應用於項目中的日誌處理,事務處理,權限處理,緩存處理等等。
image11.pngspring

Spring AOP 應用原理分析

Spring AOP底層基於代理機制(動態方式)實現功能擴展:sql

  1. 假如目標對象(被代理對象)實現接口,則底層能夠採用JDK動態代理機制爲目標對象建立代理對象(目標類和代理類會實現共同接口)。
  2. 假如目標對象(被代理對象)沒有實現接口,則底層能夠採用CGLIB代理機制爲目標對象建立代理對象(默認建立的代理類會繼承目標對象類型)。

image7.png
說明:Spring boot2.x 中AOP如今默認使用的CGLIB代理,假如須要使用JDK動態代理能夠在配置文件(applicatiion.properties)中進行以下配置:數據庫

#cglib aop proxy
#spring.aop.proxy-target-class=true
#jdk aop proxy
spring.aop.proxy-target-class=false

Spring 中AOP 相關術語分析

  • 切面(aspect): 橫切面對象,通常爲一個具體類對象(能夠藉助@Aspect聲明)。
  • 通知(Advice):在切面的某個特定鏈接點上執行的動做(擴展功能),例如around,before,after等。
  • 鏈接點(joinpoint):程序執行過程當中某個特定的點,通常指向被攔截到的目標方法。
  • 切入點(pointcut):對多個鏈接點(Joinpoint)一種定義,通常能夠理解爲多個鏈接點的集合。

image15.png

Spring AOP快速實踐

項目建立及配置

第一步建立maven項目或在已有項目基礎上添加AOP啓動依賴:編程

<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

第二步定義業務層接口api

package com.cy.pj.common.service;
public interface MailService {
    boolean sendMail(String msg);
}

第三步定義業務層實現類緩存

@Service
public class MailServiceImpl implements MailService{
      @Override
     public boolean sendMail(String msg) {//ocp(開閉原則-->對擴展開放,對修改關閉)
         long t1=System.currentTimeMillis();
         System.out.println("send->"+msg);
         long t2=System.currentTimeMillis();
         System.out.println("send time:"+(t2-t1));
         return true; 
     }
}

咱們本身計算了執行時間,可是違反了ocp原則,因此須要無侵入式擴展這個記錄執行時間的功能。咱們在這個類中添加內部類來實現兩種方式的擴展。架構

package com.cy.pj.common.service;
import org.springframework.stereotype.Service;
@Service
public class MailServiceImpl implements MailService{
        @Override
         public boolean sendMail(String msg) {//ocp(開閉原則-->對擴展開放,對修改關閉)
            //long t1=System.currentTimeMillis();
             System.out.println("send->"+msg);
            //long t2=System.currentTimeMillis();
            //System.out.println("send time:"+(t2-t1));
             return true;
     }
}
//下面的兩種設計瞭解?(基於原生方式實現功能擴展)
//本身動手寫子類重寫父類方法進行功能擴展
class TimeMailServiceImpl extends MailServiceImpl{//這種寫法的原型就是CGLIB代理機制的方式(繼承)
     @Override
     public boolean sendMail(String msg) {
         long t1=System.currentTimeMillis();
         boolean flag=super.sendMail(msg);
         long t2=System.currentTimeMillis();
         System.out.println("send time:"+(t2-t1));
         return flag;
     }
}
//本身寫兄弟類對目標對象(兄弟類)進行功能擴展,這種方式又叫組合
class TimeMailServiceImpl2 implements MailService{//這種寫法的原型就是JDK代理機制的方式(實現)
     private MailService mailService;
     public TimeMailServiceImpl2(MailService mailService){
            this.mailService=mailService;
     }
     @Override
     public boolean sendMail(String msg) {
         long t1=System.currentTimeMillis();
         boolean flag=mailService.sendMail(msg);
         long t2=System.currentTimeMillis();
         System.out.println("send time:"+(t2-t1));
         return flag;
     }
}

下來經過aop方式完成業務併發

package com.cy.pj.common.service.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect//告訴spring我是一個切面(封裝了擴展邏輯對象),這樣的對象中要包含兩部份內容(1.切入點,2.擴展邏輯-advice)
@Component//表示在spring中作一個註冊
public class SysLogAspect {
     //定義切入點
     //bean表達式爲spring中的一種粗粒度切入點表達式(不能精確到具體方法)
     //這裏的mailServiceImpl名字爲spring容器中一個bean對象的名字
     @Pointcut("bean(mailServiceImpl)")//多個bean的定義形式(bean(*ServiceImpl))
     public void doLogPointCut(){}//這個方法僅僅是承載切入點註解的一個載體,方法體內不須要寫任何內容
     
     /*按照Aspect規範定義一個@Around環繞通知*/
     //@Around("bean(mailServiceImpl)")//直接在advice註解內部定義切入點表達式
     //對於@Around註解描述的方法器規範要求
     //1)返回值類型爲Object(用於封裝目標方法的執行結果)
     //2)參數類型爲ProceedingJoinPoint(用於封裝執行的目標方法信息)
     //3)拋出的異常Throwable(用於封裝執行目標方法時拋出的異常)
     //4)在@Around註解描述的方法內部,能夠手動調用目標方法
     @Around("doLogPointCut()")//也能夠在advice註解內部經過方法引用引入切入點表達式
     public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable{
         long t1=System.currentTimeMillis();
         Object result = joinPoint.proceed();//表示調用目標方法
         long t2=System.currentTimeMillis();
         System.out.println("send time:"+(t2-t1));
         return result;
     }
}

編寫測試類來測試實現

package com.cy.pj.common.service;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class MailServiceTests {
     @Autowired
     private MailService mailService;
     @Test//面向切面的測試類
     void testSendMail03(){
            mailService.sendMail("hello mailService");
     }
     @Test//本身動手寫的子類測試
     void testSendMail01(){
    //new TimeMailServiceImpl().sendMail("hello aop");
     }
     @Test//本身動手寫的兄弟類測試
     void testSendMail02(){
    //new TimeMailServiceImpl2(new MailServiceImpl()).sendMail("hello CGB2007");
     }
}

整個aop面向切面編程的過程圖示
01-aop-start.png
debug追蹤cglib代理的注入對象
02-aop-cglib.png
debug追蹤jdk代理的注入對象
03-aop-jdk.png

應用總結分析

代理過程圖示分析

image6.png

基於JDK代理方式實現

假如目標對象有實現接口,則能夠基於JDK爲目標對象建立代理對象,而後爲目標對象進行功能擴展
image1.png
說明:假如目標對象類型沒有實現接口,則不容許使用JDK代理。

基於CGLIB代理方式實現

假如目標對象沒有實現接口(固然實現了接口也是能夠的),能夠基於CGLIB代理方式爲目標對象織入功能擴展
image3.png
說明:目標對象實現了接口也能夠基於CGLIB爲目標對象建立代理對象。可是目標對象類型假如使用了final修飾,則不可使用CGBLIB。

切面通知應用加強

通知類型

在基於Spring AOP編程的過程當中,基於AspectJ框架標準,spring中定義了五種類型的通知(通知-Advice描述的是一種擴展業務),它們分別是:

  • @Before。(目標方法執行以前執行)
  • @AfterReturning。(目標方法成功結束時執行)
  • @AfterThrowing。(目標方法異常結束時執行)
  • @After。(目標方法結束時執行)
  • @Around.(重點掌握,目標方法執行先後均可以作業務拓展)(優先級最高)
    說明:在切面類中使用什麼通知,由業務決定,並非說,在切面中要把全部通知都寫上。
package com.cy.pj.common.aspect;
@Component
@Aspect
public class SysTimeAspect {
    @Pointcut("bean(sysUserServiceImpl)")
    public void doTime(){}
 
    @Before("doTime()")
    public void doBefore(){
        System.out.println("time doBefore()");
    }
    @After("doTime()")
    public void doAfter(){
        System.out.println("time doAfter()");
    }
    /**核心業務正常結束時執行* 說明:假若有after,先執行after,再執行returning*/
    @AfterReturning("doTime()")
    public void doAfterReturning(){
        System.out.println("time doAfterReturning");
    }
    /**核心業務出現異常時執行說明:假若有after,先執行after,再執行Throwing*/
    @AfterThrowing("doTime()")
    public void doAfterThrowing(){
        System.out.println("time doAfterThrowing");
    }
    @Around("doTime()")
    public Object doAround(ProceedingJoinPoint jp)
            throws Throwable{
        System.out.println("doAround.before");
         try{
         Object obj=jp.proceed();
           System.out.println("doAround.after");
          return obj;
         }catch(Throwable e){
          System.out.println(e.getMessage());
          throw e;
         }
        
    }
}

切入點表達式加強

bean表達式(重點)

bean表達式通常應用於類級別,實現粗粒度的切入點定義,案例分析:

  • bean("userServiceImpl")指定一個userServiceImpl類中全部方法。
  • bean("*ServiceImpl")指定全部後綴爲ServiceImpl的類中全部方法。

說明:bean表達式內部的對象是由spring容器管理的一個bean對象,表達式內部的名字應該是spring容器中某個bean的name。
缺陷:不能精確到具體方法,也不能針對於具體模塊包中的方法作切入點設計

within表達式

within表達式應用於類級別,實現粗粒度的切入點表達式定義,案例分析:

  • within("aop.service.UserServiceImpl")指定當前包中這個類內部的全部方法。
  • within("aop.service.*") 指定當前目錄下的全部類的全部方法。
  • within("aop.service..*") 指定當前目錄以及子目錄中類的全部方法。

within表達式應用場景分析:
1)對全部業務bean都要進行功能加強,可是bean名字又沒有規則。
2)按業務模塊(不一樣包下的業務)對bean對象進行業務功能加強。

execution表達式

execution表達式應用於方法級別,實現細粒度的切入點表達式定義,案例分析:

語法:execution(返回值類型 包名.類名.方法名(參數列表))。

  • execution(void aop.service.UserServiceImpl.addUser())匹配addUser方法。
  • execution(void aop.service.PersonServiceImpl.addUser(String)) 方法參數必須爲String的addUser方法。
  • execution( aop.service...*(..)) 萬能配置。
@annotation表達式(重點)

@annotaion表達式應用於方法級別,實現細粒度的切入點表達式定義,案例分析

  • @annotation(anno.RequiredLog) 匹配有此註解描述的方法。
  • @annotation(anno.RequiredCache) 匹配有此註解描述的方法。

切面優先級設置實現

切面的優先級須要藉助@Order註解進行描述,數字越小優先級越高,默認優先級比較低。例如:
定義日誌切面並指定優先級。

@Order(1)
@Aspect
@Component
public class SysLogAspect {
 …
}
定義緩存切面並指定優先級:
@Order(2)
@Aspect
@Component
public class SysCacheAspect {
…
}

說明:當多個切面做用於同一個目標對象方法時,這些切面會構建成一個切面鏈,相似過濾器鏈、攔截器鏈

關鍵對象與術語總結

Spring 基於AspectJ框架實現AOP設計的關鍵對象概覽
image13.png

Spring AOP事務處理

Spring 中事務簡介

事務定義

事務(Transaction)是一個業務,是一個不可分割的邏輯工做單元,基於事務能夠更好的保證業務的正確性。

事務特性(ACID)

  • 原子性(Atomicity):一個事務中的多個操做要麼都成功要麼都失敗。
  • 一致性(Consistency): 例如存錢操做,存以前和存以後的總錢數應該是一致的。
  • 隔離性(Isolation):事務與事務應該是相互隔離的。
  • 持久性(Durability):事務一旦提交,數據要持久保存。

說明:目前市場上在事務一致性方面,一般會作必定的優化,比方說只要最終一致就能夠了,這樣的事務咱們一般會稱之爲柔性事務(只要最終一致就能夠了).

Spring 中事務管理

Spring 中事務方式概述

Spring框架中提供了一種聲明式事務的處理方式,此方式基於AOP代理,能夠將具體業務邏輯與事務處理進行解耦。也就是讓咱們的業務代碼邏輯不受污染或少許污染,就能夠實現事務控制。
在SpringBoot項目中,其內部提供了事務的自動配置,當咱們在項目中添加了指定依賴spring-boot-starter-jdbc時,框架會自動爲咱們的項目注入事務管理器對象,最經常使用的爲DataSourceTransactionManager對象。

Spring 中事務管理實現

實際項目中最經常使用的註解方式的事務管理,以註解@Transactional配置方式爲例,進行實踐分析。
基於@Transactional 註解進行聲明式事務管理的實現步驟分爲兩步:
1) 啓用聲明式事務管理,在項目啓動類上添加@EnableTransactionManagement,新版本中也可不添加(例如新版Spring Boot項目)。
2) 將@Transactional註解添加到合適的業務類或方法上,並設置合適的屬性信息。

@Transactional(timeout = 30,
 readOnly = false,
 isolation = Isolation.READ_COMMITTED,
 rollbackFor = Throwable.class,
 propagation = Propagation.REQUIRED)
 @Service
 public class implements SysUserService {
 @Transactional(readOnly = true)
 @Override
 public PageObject<SysUserDeptVo> findPageObjects(
 String username, Integer pageCurrent) {
 …
 }
}

其中,代碼中的@Transactional註解用於描述類或方法,告訴spring框架咱們要在此類的方法執行時進行事務控制,其具體說明以下:。
▪ 當@Transactional註解應用在類上時表示類中全部方法啓動事務管理,而且通常用於事務共性的定義。
▪ 當@Transactional描述方法時表示此方法要進行事務管理,假如類和方法上都有@Transactional註解,則方法上的事務特性優先級比較高。

@Transactional經常使用屬性應用說明:
▪ timeout:事務的超時時間,默認值爲-1,表示沒有超時顯示。若是配置了具體時間,則超過該時間限制但事務尚未完成,則自動回滾事務。這個時間的記錄方式是在事務開啓之後到sql語句執行以前。
▪ read-only:指定事務是否爲只讀事務,默認值爲 false;爲了忽略那些不須要事務的方法,好比讀取數據,能夠設置read-only爲true。對添加,修改,刪除業務read-only的值應該爲false。
▪ rollback-for:用於指定可以觸發事務回滾的異常類型,若是有多個異常類型須要指定,各種型之間能夠經過逗號分隔。
▪ no-rollback- for: 拋出no-rollback-for 指定的異常類型,不回滾事務。
▪ isolation:事務的隔離級別,默認值採用 DEFAULT。當多個事務併發執行時,可能會出現髒讀,不可重複讀,幻讀等現象時,但假如不但願出現這些現象可考慮修改事務的隔離級別(但隔離級別越高併發就會越小,性能就會越差)

Spring中事務控制過程分析
image2.png

Spring事務管理是基於接口代理(JDK)或動態字節碼(CGLIB)技術,而後經過AOP實施事務加強的。當咱們執行添加了事務特性的目標方式時,系統會經過目標對象的代理對象調用DataSourceTransactionManager對象,在事務開始的時,執行doBegin方法,事務結束時執行doCommit或doRollback方法。

Spring 中事務傳播特性
事務傳播(Propagation)特性指"不一樣業務(service)對象"中的事務方法之間相互調用時,事務的傳播方式
image16.png
其中,經常使用事務傳播方式:

@Transactional(propagation=Propagation.REQUIRED)。

若是沒有事務建立新事務, 若是當前有事務參與當前事務, Spring 默認的事務傳播行爲是PROPAGATION_REQUIRED,它適合於絕大多數的狀況。假設 ServiveX#methodX() 都工做在事務環境下(即都被 Spring 事務加強了),假設程序中存在以下的調用鏈:
Service1#method1()->Service2#method2()->Service3#method3(),那麼這 3 個服務類的 3 個方法經過 Spring 的事務傳播機制都工做在同一個事務中。

image17.png

@Transactional(propagation = Propagation.REQUIRED)
    @Override
    public List<Node> findZtreeMenuNodes() {
        return sysMenuDao.findZtreeMenuNodes();
    }

當有一個業務對象調用如上方法時,此方法始終工做在一個已經存在的事務方法,或者是由調用者建立的一個事務方法中。

@Transactional(propagation=Propagation.REQUIRES_NEW)。

必須是新事務, 若是有當前事務, 掛起當前事務而且開啓新事務,
image9.png

@Transactional(propagation = Propagation.REQUIRES_NEW)
 @Override
 public void saveObject(SysLog entity) {
    sysLogDao.insertObject(entity);
 }

當有一個業務對象調用如上業務方法時,此方法會始終運行在一個新的事務中。

Spring AOP 異步操做實現

異步場景分析

在開發系統的過程當中,一般會考慮到系統的性能問題,提高系統性能的一個重要思想就是「串行」改「並行」。提及「並行」天然離不開「異步」,今天咱們就來聊聊如何使用Spring的@Async的異步註解。

Spring 業務的異步實現

啓動異步配置

在基於註解方式的配置中,藉助@EnableAsync註解進行異步啓動聲明,Spring Boot版的項目中,將@EnableAsync註解應用到啓動類上

@EnableAsync //spring容器啓動時會建立線程池
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Spring中@Async註解應用

在須要異步執行的業務方法上,使用@Async方法進行異步聲明。

@Async
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void saveObject(SysLog entity) {
     System.out.println("SysLogServiceImpl.save:"+
     Thread.currentThread().getName());
     sysLogDao.insertObject(entity);
     //try{Thread.sleep(5000);}catch(Exception e) {}
 }

假如須要獲取業務層異步方法的執行結果,可參考以下代碼設計進行實現:

@Transactional(propagation = Propagation.REQUIRES_NEW)
    @Async
    @Override
    public Future<Integer> saveObject(SysLog entity) {
        System.out.println("SysLogServiceImpl.save:"+
        Thread.currentThread().getName());
        int rows=sysLogDao.insertObject(entity);
        //try{Thread.sleep(5000);}catch(Exception e) {}
        return new AsyncResult<Integer>(rows);
    }

其中,AsyncResult對象能夠對異步方法的執行結果進行封裝,假如外界須要異步方法結果時,能夠經過Future對象的get方法獲取結果。

當咱們須要本身對spring框架提供的線程池進行一些簡易配置

spring:
  task:
    execution:
      pool:
        queue-capacity: 128
        core-size: 5
        max-size: 128
        keep-alive: 60000
      thread-name-prefix: db-service-task-

對於spring框架中線程池配置參數的涵義,能夠參考ThreadPoolExecutor對象中的解釋。

說明:對於@Async註解默認會基於ThreadPoolTaskExecutor對象獲取工做線程,而後調用由@Async描述的方法,讓方法運行於一個工做線程,以實現異步操做。可是假如系統中的默認拒絕處理策略,任務執行過程的異常處理不能知足咱們自身業務需求的話,我能夠對異步線程池進行自定義.(SpringBoot中默認的異步配置能夠參考自動配置對象TaskExecutionAutoConfiguration).

Spring 自定義異步池的實現(拓展)

爲了讓Spring中的異步池更好的服務於咱們的業務,同時也儘可能避免OOM,能夠自定義線程池優化設計以下:

package com.cy.pj.common.config
@Slf4j
@Setter
@Configuration
@ConfigurationProperties("async-thread-pool")
public class SpringAsyncConfig implements AsyncConfigurer{
    /**核心線程數*/
    private int corePoolSize=20;
    /**最大線程數*/
    private int maximumPoolSize=1000;
    /**線程空閒時間*/
    private int keepAliveTime=30;
    /**阻塞隊列容量*/
    private int queueCapacity=200;
    /**構建線程工廠*/
    private ThreadFactory threadFactory=new ThreadFactory() {
        //CAS算法
        private AtomicInteger at=new AtomicInteger(1000);
        @Override
        public Thread newThread(Runnable r) {
            return new Thread(r, 
"db-async-thread-"+at.getAndIncrement());
        }
    };    
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maximumPoolSize);
        executor.setKeepAliveSeconds(keepAliveTime);
        executor.setQueueCapacity(queueCapacity);
        executor.setRejectedExecutionHandler((Runnable r, 
 ThreadPoolExecutor exe) -> {
                log.warn("當前任務線程池隊列已滿.");
        });
        executor.initialize();
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler 
getAsyncUncaughtExceptionHandler() {
        return new AsyncUncaughtExceptionHandler() {
            @Override
            public void handleUncaughtException(Throwable ex ,
 Method method , Object... params) {
                log.error("線程池執行任務發生未知異常.", ex);
            }
        };
    }}

其中:@ConfigurationProperties("async-thread-pool")的含義是讀取application.yml配置文件中以"async-thread-pool"名爲前綴的配置信息,並經過所描述類的set方法賦值給對應的屬性,在application.yml中鏈接器池的關鍵配置以下:

async-thread-pool:
       corePoolSize: 20
       maxPoolSize: 1000
       keepAliveSeconds: 30
       queueCapacity: 1000

後續在業務類中,假如咱們使用@Async註解描述業務方法,默認會使用ThreadPoolTaskExecutor池對象中的線程執行異步任務。

Spring AOP中Cache操做實現

緩存場景分析

在業務方法中咱們可能調用數據層方法獲取數據庫中數據,假如訪問數據的頻率比較高,爲了提升的查詢效率,下降數據庫的訪問壓力,能夠在業務層對數據進行緩存.

Spring 中業務緩存應用實現過程

啓動緩存配置

在項目(SpringBoot項目)的啓動類上添加@EnableCaching註解,以啓動緩存配置。

package com.cy;
/**
* 異步的自動配置生效).
 * @EnableCaching 註解表示啓動緩存配置
 */
@EnableCaching
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

業務方法上應用緩存配置

在須要進行緩存的業務方法上經過@Cacheable註解對方法進行相關描述.表示方法的返回值要存儲到Cache中,假如在更新操做時須要將cache中的數據移除,能夠在更新方法上使用@CacheEvict註解對方法進行描述。
第一步:在相關模塊查詢相關業務方法中,使用緩存

@Cacheable(value = "menuCache")
@Transactional(readOnly = true)
public List<Map<String,Object>> findObjects() {
....
}

其中,value屬性的值表示要使用的緩存對象,名字本身指定,其中底層爲一個map對象,當向cache中添加數據時,key默認爲方法實際參數的組合。
第二步:在相關模塊更新時,清除指定緩存數據
allEntries表示清除全部。

@CacheEvict(value="menuCache",allEntries=true)
 @Override
 public int saveObject(SysDept entity) {...}

spring中的緩存應用原理
image14.png

進行一個小案例

第一步定義業務接口

package com.cy.pj.module.service;
import java.util.List;
public interface ModuleService {
    List<String> findPermissions();
}

第二步定義業務實現類

package com.cy.pj.module.service;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class ModuleServiceImpl implements ModuleService{
    @Override
     public List<String> findPermissions() {
         System.out.println("select permissions from database");
         List<String> list=new ArrayList<>();
         list.add("sys:log:delete");
         list.add("sys:log:select");//假設這些數據來自數據庫
         return list;
     }
}

第三步定義切面

package com.cy.pj.commom.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Component
@Aspect
public class CacheAspect {//系統底層會將這個切面中的內容轉換爲Advisor對象

    //假設這個map就是咱們的一個小cache,咱們從數據庫取出的數據能夠存儲到此cache中
    private Map<String,Object> cache=new ConcurrentHashMap<>();

    @Pointcut("bean(moduleServiceImpl)")
    public void doCache(){}

    @Around("doCache()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
        //1.從cache中取數據
        Object obj = cache.get("userPer");//假設key爲userPer
        if(obj!=null) return obj;
        //2.cache中沒有則查數據
        obj= joinPoint.proceed();
        //3.將數據存儲到cache
        cache.put("userPer", obj);
        return obj;
    }
}

第四步編寫測試類測試

package com.cy.pj.module.service;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
public class ModuleServiceTests {

    @Autowired
    private ModuleService moduleService;

    @Test
    void testFindPermissions(){
        List<String> permissions = moduleService.findPermissions();
        permissions = moduleService.findPermissions();
        permissions = moduleService.findPermissions();
    }
}

Spring AOP原生方式實現

Spring 整合AspectJ框架實現AOP只是Spring框架中AOP的一種實現方式,此方式相對比較簡單,實現方便。但此方式底層仍是要轉換爲Spring原生AOP的實現,Spring AOP原生方式實現的核心有兩大部分構成,分別是:
▪ 代理(JDK,CGLIB)。
▪ org.aopalliance包下的攔截體系。

以Spring中一種原生AOP架構的基本實現爲例進行原理分析和說明,其簡易架構
image10.png
其中DefaultAdvisorAutoProxyCreator這個類功能更爲強大,這個類的奇妙之處是他實現BeanPostProcessor接口,當ApplicationContext讀取全部的Bean配置信息後,這個類將掃描上下文,尋找全部的Advisor對象(一個Advisor由切入點和通知組成),將這些Advisor應用到全部符合切入點的Bean中。

核心業務接口定義及實現

定義郵件業務接口,用於定義搜索業務規範

package com.cy.pj.common.service;
public interface MailService {
    boolean sendMsg(String message);
}

定義郵件業務接口實現

package com.cy.pj.common.service;

import org.springframework.stereotype.Service;

@Service
public class MailServiceImpl implements MailService{

    @Override
    public boolean sendMsg(String message) {
        System.out.println("send->"+message);
        return true;
    }

}

定義LogAdvice對象,基於此對象爲目標業務對象作日誌加強。

其中,MethodInterceptor對象繼承Advice對象,基於此對象方法能夠對目標方法進行攔截。

package com.cy.pj.common.advisor;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

/**封裝了擴展業務邏輯的對象,這樣的對象在原生的aop中須要在advisor中註冊*/
public class LogAdvice implements MethodInterceptor {//Advice

    /**此方法能夠在目標業務方法執行以前和以後添加擴展邏輯*/
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        System.out.println("start:"+System.nanoTime());
        Object result = methodInvocation.proceed();//執行目標方法
        System.out.println("end:"+System.nanoTime());
        return result;
    }
}

日誌Advisor對象定義及實現

其中,StaticMethodMatcherPointcutAdvisor類爲Spring框架中定義的一種Advisor,咱們本身寫的Advisor能夠直接繼承此類進行資源整合。

package com.cy.pj.common.advisor;

import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 此Advisor中定義了一種規範
 * 1)定義了哪些方法爲切入點方法
 * 2)定義了在切入點方法執行時要植入的通知(擴展邏輯)
 */
@Component
public class LogAdvisor extends StaticMethodMatcherPointcutAdvisor {
    //定通知 advice
    public LogAdvisor(){
        setAdvice(new LogAdvice());
    }

    //定切點 pointcut
    /**
     * matches方法中能夠獲取咱們要執行的目標方法,而且咱們能夠在此判斷這個目標方法是否爲咱們的一個切入點方法
     * 1)返回值爲true表示目標方法爲切入點方法(在此方法執行時能夠植入擴展邏輯)
     * 2)返回值爲false表示目標方法爲非切入點方法
     */
    @Override
    public boolean matches(Method method, Class<?> aClass) {
        try {
            Method targetMethod =
                    aClass.getMethod(method.getName(), method.getParameterTypes());
            return targetMethod.getName().equals("sendMsg");
        }catch (Exception e){
            return false;
        }
    }
}

BeanPostProcessor類型對象初始化

在項目啓動類中,添加DefaultAdvisorAutoProxyCreator對象初始化方法,基於此對象在容器啓動時掃描全部Advisor對象,而後基於切入點描述的目標方法爲目標對象建立代理對象

package com.cy;

import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class Application {

    @Bean //@Bean註解描述方法時,這個方法的返回值交給spring管理
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
        return new DefaultAdvisorAutoProxyCreator();
    }
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

基於Spring boot項目進行單元測試

package com.cy.pj.common.service;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class MailServiceTests {
    @Autowired
    private MailService mailService;

    @Test
    void testSendMsg(){
        System.out.println(mailService.sendMsg("hello spring aop"));
    }
}

原生實現AOP的過程分析圖

04-aop-spring.png

相關文章
相關標籤/搜索