慕課網《探秘Spring AOP》學習總結

at_1發表於2021-09-09

慕課網《探秘Spring AOP》學習總結

時間:2017年09月03日星期日
說明:本文部分內容均來自慕課網。@慕課網: 
教學原始碼:
學習原始碼:
第一章:課程介紹
1-1 面向切面

課程章節

概覽
AOP使用
AOP原理
AOP開源運用
課程實戰
課程總結

面向切面程式設計是一種程式設計正規化

程式設計正規化概覽

程式導向程式設計
物件導向程式設計
面向函式程式設計(函數語言程式設計)
事件驅動程式設計(GUI開發中比較常見)
面向切面程式設計

AOP是什麼

是一種程式設計正規化,不是程式語言
解決特定問題,不能解決所有問題
是OOP的補充,不是替代

AOP的初衷

DRY:Don’t Repeat Yourself程式碼重複性問題
SOC:Separation of Concerns關注點分離
    -水平分離:展示層->服務層->持久層
    -垂直分離:模組劃分(訂單、庫存等)
    -切面分離:分離功能性需求與非功能性需求

使用AOP的好處

集中處理某一關注點/橫切邏輯
可以很方便地新增/刪除關注點
侵入性少,增強程式碼可讀性及可維護性

AOP的應用場景

許可權控制
快取控制
事務控制
審計日誌
效能監控
分散式追蹤
異常處理

支援AOP的程式語言

Java
.NET
C/C++
Ruby
Python
PHP
…
1-2 簡單案例

案例背景

產品管理的服務
產品新增、刪除的操作只能管理員才能進行
普通實現VS AOP實現

建立一個名為springaopguide的maven專案pom如下

4.0.0com.myimoocspringaopguide0.0.1-SNAPSHOTjarspringaoporg.springframework.bootspring-boot-starter-parent1.5.1.RELEASE<!-- lookup parent from repository --&gtUTF-8UTF-81.8org.springframework.bootspring-boot-starter-aoporg.springframework.bootspring-boot-starter-testtestorg.springframework.bootspring-boot-maven-plugin

完成後的專案結構如下

圖片描述

程式碼編寫

1.編寫Product類

package com.myimooc.springaopguide.domain;

/**
 * @title 產品領域模型
 * @describe 產品實體物件
 * @author zc
 * @version 1.0 2017-09-03
 */
public class Product {

    private Long id;

    private String name;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

2.編寫CurrentUserHolder類

package com.myimooc.springaopguide.security;

/**
 * @title 獲取使用者資訊
 * @describe 模擬使用者的切換,將使用者資訊存入當前執行緒
 * @author zc
 * @version 1.0 2017-09-03
 */
public class CurrentUserHolder {

    private static final ThreadLocal holder = new ThreadLocal();

    public static String get(){
        return holder.get() == null ? "unkown" : holder.get();
    }

    public static void set(String user){
        holder.set(user);
    }
}

3.編寫AdminOnly類

package com.myimooc.springaopguide.security;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @title 管理員許可權註解
 * @describe 被該註解宣告的方法需要管理員許可權
 * @author zc
 * @version 1.0 2017-09-03
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AdminOnly {

}

4.編寫SecurityAspect類

package com.myimooc.springaopguide.security;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.myimooc.springaopguide.service.AuthService;

/**
 * @title 許可權校驗切面類
 * @describe 
 * @author zc
 * @version 1.0 2017-09-03
 */
// 宣告為一個切面
@Aspect
@Component
public class SecurityAspect {

    @Autowired
    private AuthService authService;

    // 使用要攔截標註有AdminOnly的註解進行操作
    @Pointcut("@annotation(AdminOnly)")
    public void adminOnly(){

    }

    @Before("adminOnly()")
    public void check(){
        authService.checkAccess();
    }

}

5.編寫AuthService類

package com.myimooc.springaopguide.service;

import java.util.Objects;

import org.springframework.stereotype.Service;

import com.myimooc.springaopguide.security.CurrentUserHolder;

/**
 * @title 許可權校驗類
 * @describe 對使用者許可權進行校驗
 * @author zc
 * @version 1.0 2017-09-03
 */
@Service
public class AuthService {

    public void checkAccess(){
        String user = CurrentUserHolder.get();
        if(!Objects.equals("admin", user)){
            throw new RuntimeException("operation not allow");
        }
    }

}

6.編寫ProductService類

package com.myimooc.springaopguide.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.myimooc.springaopguide.domain.Product;

/**
 * @title 產品服務類
 * @describe 產品相關業務服務-傳統方式實現許可權校驗
 * @author zc
 * @version 1.0 2017-09-03
 */
@Service
public class ProductService {

    @Autowired
    private AuthService AuthService;

    public void insert(Product product){
        AuthService.checkAccess();
        System.out.println("insert product");
    }

    public void delete(Long id){
        AuthService.checkAccess();
        System.out.println("delete product");
    }

}

7.編寫ProductServiceAop類

package com.myimooc.springaopguide.service;

import org.springframework.stereotype.Service;

import com.myimooc.springaopguide.domain.Product;
import com.myimooc.springaopguide.security.AdminOnly;

/**
 * @title 產品服務類
 * @describe 產品相關業務服務-AOP方式實現許可權校驗
 * @author zc
 * @version 1.0 2017-09-03
 */
@Service
public class ProductServiceAop {

    @AdminOnly
    public void insert(Product product){
        System.out.println("insert product");
    }

    @AdminOnly
    public void delete(Long id){
        System.out.println("delete product");
    }

}

8.編寫AopGuideApplicationTests類

package com.myimooc.springaopguide;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import com.myimooc.springaopguide.security.CurrentUserHolder;
import com.myimooc.springaopguide.service.ProductService;
import com.myimooc.springaopguide.service.ProductServiceAop;

/**
 * @title 單元測試類
 * @describe 測試許可權校驗服務是否生效
 * @author zc
 * @version 1.0 2017-09-03
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class AopGuideApplicationTests {

    @Autowired
    private ProductService productService;

    @Test(expected = Exception.class)
    public void annoInsertTest(){
        CurrentUserHolder.set("tom");
        productService.delete(1L);
    }

    @Test
    public void adminInsertTest(){
        CurrentUserHolder.set("admin");
        productService.delete(1L);
    }

    @Autowired
    private ProductServiceAop productServiceAop;

    @Test(expected = Exception.class)
    public void annoInsertAopTest(){
        CurrentUserHolder.set("tom");
        productServiceAop.delete(1L);
    }

    @Test
    public void adminInsertAopTest(){
        CurrentUserHolder.set("admin");
        productServiceAop.delete(1L);
    }

}
第二章:使用詳解
2-1 本節內容

Spring AOP使用方式

XML配置+Pointcut expression【不推薦使用方式】
註解方式+ Pointcut expression【推薦使用該方式】

Aspectj註解

@Aspect:用於宣告當前類是一個切面
@Pointcut:用於描述在哪些類、哪些方法上執行切面的程式碼
Advice:描述想要在這些方法執行的什麼時機進行攔截

本章內容

Pointcut express:切面表示式
5種Advice:建言的五種細分怎麼使用
2-2 切面表示式

切面表示式

1.designators(指示器)
    execution()
    描述透過什麼樣的方式去匹配哪些類、哪些方法
2.wildcards(萬用字元)
    * .. +
    使用萬用字元進行描述
3.operators(運算子)
    && || !
    使用運算子進行多條件的判斷

Designators(指示器)

匹配方法 execution()
匹配註解 @target() @args() @within() @annotation()
匹配包/型別 @within()
匹配物件 this() bean() target()
匹配引數 args()

Wildcards(萬用字元)

* 匹配任意數量的字元
+ 匹配指定類及其子類
.. 一般用於匹配任意引數的子包或引數

Operators(運算子)

&& 與運算子
|| 或運算子
! 非運算子
2-3 匹配包類
    // 匹配 ProductServiceAop 類裡面的所有方法
    @Pointcut("within(com.myimooc.springaopguide.service.ProductServiceAop)")
    public void matchType(){}

    // 匹配 com.myimooc.springaopguide.service 包及子包下所有類的方法
    @Pointcut("within(com.myimooc.springaopguide.service..*)")
    public void matchPackage(){}
2-4 匹配物件
    // 匹配AOP物件的目標物件為指定型別的方法,即DemoDao的aop代理物件的方法
    @Pointcut("this(com.myimooc.springaopguide.dao.DemoDao)")
    public void testDemo(){}

    // 匹配實現IDao介面的目標物件(而不是aop代理後的物件)的方法,這裡即DemoDao的方法
    @Pointcut("target(com.myimooc.springaopguide.dao.IDao)")
    public void targetDemo(){}

    // 匹配所有以Service結尾的bean裡面的方法
    @Pointcut("bean(*Service)")
    public void beanDemo(){}
2-5 匹配引數
    // 匹配任何以find開頭而且只有一個Long引數的方法
    @Pointcut("execution(* *..find*(Long))")
    public void argsDemo1(){}

    // 匹配任何只有一個Long引數的方法
    @Pointcut("args(Long)")
    public void argsDemo2(){}

    // 匹配任何以find開頭而且第一個引數為Long型的方法
    @Pointcut("execution(* *..find*(Long,..))")
    public void argsDemo3(){}

    // 匹配第一個引數為Long型的方法
    @Pointcut("args(Long,..))")
    public void argsDemo4(){}
2-6 匹配註解
    // 匹配方法標註有AdminOnly的註解的方法
    @Pointcut("@annotation(com.myimooc.springaopguide.security.AdminOnly)")
    public void annoDemo(){}

    // 匹配標註有Beta的類底下的方法,要求的annotation的RetentionPolicy級別為CLASS
    @Pointcut("@within(com.google.common.annotations.Beta)")
    public void annoWithDemo(){}

    // 匹配標註有Repository的類底下的方法,要求的RetentionPolicy級別為RUNTIME
    @Pointcut("@target(org.springframework.stereotype.Repository)")
    public void annoTargetDemo(){}

    // 匹配傳入的引數類標註有Repository註解的方法
    @Pointcut("@args(org.springframework.stereotype.Repository)")
    public void annoArgsDemo(){}
2-7 匹配方法

execution()格式

execution(
    modifier-pattern? // 修飾符匹配
    ret-type-pattern // 返回值匹配
    declaring-type-pattern? // 描述值包名
    name-pattern(param-pattern) // 方法名匹配(引數匹配)
    throws-pattern?// 丟擲異常匹配
)

execution()例項

    // 匹配 使用public修飾符 任意返回值 在com.myimooc.springaopguide.service包及子下 
    // 以Service結尾的類 任意方法(任意引數)
    @Pointcut("execution(public * com.myimooc.springaopguide.service..*Service.*(..))")
    public void matchCondition(){}
2-8 建言註解

5中Advice(建言)註解

@Before,前置通知
@After(finally),後置通知,方法執行完之後
@AfterReturning,返回通知,成功執行之後
@AfterThrowing,異常通知,丟擲異常之後
@Around,環繞通知

5中Advice(建言)例項

    // 定義切點,攔截使用NeedSecured註解修飾的方法
    @Pointcut("@within(com.myimooc.demo.security.NeedSecured)")
    public void annoTargetVsWithinDemo(){}

    // 使用NeedSecured註解修飾 且 在com.myimooc包下的方法
    @Before("annoTargetVsWithinDemo() && within(com.myimooc..*)")
    public void beforeDemo(){
        System.out.println("被攔截方法執行之前執行");
    }

    @After("annoTargetVsWithinDemo() && within(com.myimooc..*)")
    public void afterDemo(){
        System.out.println("被攔截方法執行之後執行");
    }

    @AfterReturning("annoTargetVsWithinDemo() && within(com.myimooc..*)")
    public void afterReturning(){
        System.out.println("程式碼成功之後執行");
    }

    @AfterThrowing("annoTargetVsWithinDemo() && within(com.myimooc..*)")
    public void afterThrowing(){
        System.out.println("程式碼執行丟擲異常之後執行");
    }

    @Around("annoTargetVsWithinDemo() && within(com.myimooc..*)")
    public Object aroundDemo(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println("相當於@Before");
        try{
            Object result = pjp.proceed(pjp.getArgs());
            System.out.println("相當於@AfterReturning");
            return result;
        }catch (Throwable throwable) {
            System.out.println("相當於@AfterThrowing");
            throw throwable;
        }finally {
            System.out.println("相當於@After");
        }
    }

Advice中的引數及結果繫結

@Before("annoTargetVsWithinDemo() && within(com.myimooc..*) && args(userId)")
    public void beforeWithArgs(JoinPoint joinPoint,Long userId){
        System.out.println("被攔截方法執行之前執行,args:"+userId);
    }

    @AfterReturning(value="annoTargetVsWithinDemo() && within(com.myimooc..*)",returning="returnValue")
    public void getResult(Object returnValue){
        if(returnValue != null){
            System.out.println("程式碼成功之後執行,result:"+returnValue);
        }
    }
第三章:實現原理
3-1 本節內容

上節回顧

Pointcut expression的組成部分
各種designators的區別
5中advice及引數、結果繫結

實現原理

概述
設計:代理模式、責任鏈模式
實現:JDK實現、cglib實現
3-2 原理概述

原理概述:植入的時機

1.編譯期(AspectJ)
2.類載入時(Aspectj 5+)
3.執行時(Spring AOP)【本節課講解內容】

執行時植入

執行時植入是怎麼實現的
從靜態代理到動態代理
基於介面代理與基於繼承代理
3-3 代理模式

代理AOP物件

Caller:呼叫方
Proxy:AOP代理物件
Target:目標物件

代理模式類圖

圖片描述

客戶端透過介面來引用目標物件
代理物件把真正的方法委託目標物件來執行,自己執行額外的邏輯

程式碼編寫

1.編寫Subject類

2.編寫RealSubject類

3.編寫Proxy類

4.編寫Client類

受篇幅限制,原始碼請到我的github地址檢視

3-4 JDK代理

靜態代理與動態代理

靜態代理的缺點:每當需要代理的方法越多的時候,重複的邏輯就越多
動態代理的兩類實現:基於介面代理與基於繼承代理
兩類實現的代表技術:JDK代理與Cglib代理

JDK實現要點

類:java.lang.reflect.Proxy
介面:InvocationHandler
只能基於介面進行動態代理

程式碼編寫

1.編寫JdkSubject類

2.編寫Client類

受篇幅限制,原始碼請到我的github地址檢視

3-5 JDK解析

JDK代理原始碼解析

Proxy.newProxyInstance(首先,呼叫該方法)
getProxyClass0、ProxyClassFactory、ProxyGenerator(然後,分別呼叫方法,生成位元組碼)
newInstance(最後,利用反射根據位元組碼生成例項)
3-6 Cglib代理

程式碼編寫

1.編寫DemoMethodInterceptor類

2.編寫Client類

受篇幅限制,原始碼請到我的github地址檢視

JDK與Cglib代理對比

JDK只能針對有介面的類的介面方法進行動態代理
Cglib基於繼承來實現代理,無法對static、final類進行代理
Cglib基於繼承來實現代理,無法對private、static方法進行代理
3-7 Spring選擇

Spring建立代理bean時序圖

圖片描述

SpringAOP對兩種實現的選擇

如果目標物件實現了介面,則預設採用JDK動態代理
如果目標物件沒有實現介面,則採用Cglib進行動態代理
如果目標物件實現了介面,但設定強制cglib代理,則使用cglib代理
在SpringBoot中,透過@EnableAspectJAutoProxy(proxyTargetClass=true)設定
3-8 鏈式呼叫

當多個AOP作用到同一個目標物件時,採用責任鏈模式

責任鏈模式類圖

圖片描述

程式碼編寫

1.編寫Handler類

2.編寫Client類

3.編寫Chain類

4.編寫ChainHandler類

5.編寫ChainClient類

受篇幅限制,原始碼請到我的github地址檢視

第四章:程式碼解讀
4-1 本節內容

上節回顧

靜態代理與動態代理
JDK代理與Cglib代理區別及侷限
代理模式與責任鏈模式

Spring AOP在開源專案裡面的應用:三個例子

事務:@Transactional:Spring如何利用Transaction進行事務控制
安全:@PreAuthorize:Spring Security如何利用PreAuthorize進行安全控制
快取:@Cacheable:Spring Cache如何利用Cacheable進行快取控制

透過案例來講解,原始碼可到我的github地址檢視

第五章:實戰案例
5-1 案例背景

實戰案例背景

商家產品管理系統
記錄產品修改的操作記錄
什麼人在什麼時間修改了哪些產品的哪些欄位修改為什麼值

實現思路

利用aspect去攔截增刪改方法
利用反射獲取物件的新舊值
利用@Around的advice去記錄操作記錄
5-2 案例實現

建立名為mydatalog的maven專案pom如下

4.0.0com.myimoocmydatalog0.0.1-SNAPSHOTjarmydatalogorg.springframework.bootspring-boot-starter-parent1.5.2.RELEASE<!-- lookup parent from repository --&gtUTF-8UTF-81.8org.springframework.bootspring-boot-starter-aoporg.springframework.bootspring-boot-starter-data-jpa<!--        
            org.springframework.boot
            spring-boot-starter-data-mongodb
         --&gtmysqlmysql-connector-javaorg.springframework.bootspring-boot-starter-testtestcommons-beanutilscommons-beanutilscom.alibabafastjson1.2.36

完成後的專案結構圖如下

圖片描述

受篇幅限制,原始碼請到我的github地址檢視

第六章:課程總結
6-1 課程總結

要點清單

AOP的適用範圍及優劣勢
AOP的概念及Spring切面表示式
AOP的實現原理及運用

使用SpringAOP的注意事項

不宜把重要的業務邏輯放到AOP中處理
無法攔截static、final、private方法
無法攔截內部方法呼叫

課程小結

合理利用面向切面程式設計提高程式碼質量
掌握SpringAOP概念及實現原理
瞭解AOP的優缺點及SpringAOP的使用侷限

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/1817/viewspace-2808096/,如需轉載,請註明出處,否則將追究法律責任。

相關文章