[Spring] Spring AOP 實現原理剖析(一)

手機用戶請橫屏獲取最佳閱讀體驗,REFERENCES中是本文參考的鏈接,如需要鏈接和更多資源,可以關注其他博客發佈地址。

平臺 地址
CSDN https://blog.csdn.net/sinat_28690417
簡書 https://www.jianshu.com/u/3032cc862300
個人博客 https://yiyuery.github.io/NoteBooks/

[Spring] Spring AOP 實現原理剖析(一)

Spring AOP 實現原理剖析

AOP 幾個重要術語

  • 連接點 Joinpoint
  • 切點 Pointcut
  • 增強 Advice
  • 目標對象 Target
  • 引介 Introduction
  • 織入 Weaving
  • 代理 Proxy
  • 切面 Aspect

此處不對這幾個術語做冗長的介紹,爲了便於記憶,上面的元素在AOP中充當何種角色?我們隨着實戰的深入慢慢來講。

AOP的工作重心在於如何將增強應用於目標對象的連接點上,這裏主要包括2個工作:

  1. 如何通過切點和增強定位到連接點?
  2. 如何在增強中編寫切面的代碼?

AOP的常見工具

  • AspectJ
  • AspectWerkz
  • JBoss AOP
  • Spring AOP

AOP工具的設計目標是把業務的模塊化分割通過橫切來實現。使用類似OOP的方式進行切面的編程工作。位於AOP工具核心的時連接點模型,它提供一種機制,可以定位到需要在何處發生橫切。

基礎知識

Spring AOP

  • 運用動態代理技術在運行期織入增強的代碼
  • Spring AOP 運用了2種代理機制:一種是基於JDK的動態代理,另一種是基於CGLib的動態代理。

使用2種代理機制的主要原因是因爲JDK本身只提供了接口的代理,而不支持類的代理。

橫切邏輯

先寫個簡單的橫切邏輯

/*
 * @ProjectName: 編程學習
 * @Copyright:   2019 HangZhou xiazhaoyang Dev, Ltd. All Right Reserved.
 * @address:     https://yiyuery.github.io/NoteBooks/
 * @date:        2019/12/9 8:26 下午
 * @email:       [email protected]
 * @description: 本內容僅限於編程技術學習使用,轉發請註明出處.
 */
package com.example.spring.aop.simple;

import lombok.extern.slf4j.Slf4j;
import sun.nio.cs.UTF_32LE;

/**
 * <p>
 * 業務方式執行監視器
 * </p>
 *
 * @author xiazhaoyang
 * @version V1.0.0
 * @date 2019/12/9 8:26 下午
 * @modificationHistory=========================邏輯或功能性重大變更記錄
 * @modify By: {修改人}  2019/12/9
 * @modify reason: {方法名}:{原因}
 * ...
 */
@Slf4j
public class BusinessLogMonitor {

    /**
     * 線程同步(緩存監視器)
     */
    private static ThreadLocal<BusinessLogHandler> handlerThreadLocal = new ThreadLocal<>();

    /**
     * 初始化業務日誌處理器
     * @param method
     */
    public static void begin(String method){
        log.info("begin monitor...");
        BusinessLogHandler handler = new  BusinessLogHandler(method);
        handlerThreadLocal.set(handler);
    }

    /**
     * 結束並打印業務日誌
     */
    public static void end(){
        log.info("end monitor....");
        BusinessLogHandler handler = handlerThreadLocal.get();
        handler.businessLog();
        handlerThreadLocal.remove();
    }
}

/*
 * @ProjectName: 編程學習
 * @Copyright:   2019 HangZhou xiazhaoyang Dev, Ltd. All Right Reserved.
 * @address:     https://yiyuery.github.io/NoteBooks/
 * @date:        2019/12/9 8:28 下午
 * @email:       [email protected]
 * @description: 本內容僅限於編程技術學習使用,轉發請註明出處.
 */
package com.example.spring.aop.simple;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

/**
 * <p>
 *  記錄業務方法執行信息
 *  如執行耗時等
 * </p>
 *
 * @author xiazhaoyang
 * @version V1.0.0
 * @date 2019/12/9 8:28 下午
 * @modificationHistory=========================邏輯或功能性重大變更記錄
 * @modify By: {修改人}  2019/12/9
 * @modify reason: {方法名}:{原因}
 * ...
 */
@Setter
@Getter
@Slf4j
public class BusinessLogHandler {

    private long begin;
    private long end;
    private String serviceMethod;

    /**
     * 構造函數
     * 記錄開始時間和業務方法名稱
     * @param serviceMethod
     */
    public BusinessLogHandler(String serviceMethod) {
        this.serviceMethod = serviceMethod;
        setBegin(System.currentTimeMillis());
    }

    /**
     *
     * 記錄結束時間
     * 打印業務方法執行耗時日誌
     */
    public void businessLog() {
        setEnd(System.currentTimeMillis());
        log.info(getServiceMethod() + "執行耗時" + getElapse() + "毫秒");
    }

    /**
     * 計算耗時
     * @return 方法運行耗時
     */
    private long getElapse() {
        return getEnd() - getBegin();}
}


定義簡單的個人員管理服務類,並把我們寫的這個簡單的業務日誌監聽邏輯寫入

/*
 * @ProjectName: 編程學習
 * @Copyright:   2019 HangZhou xiazhaoyang Dev, Ltd. All Right Reserved.
 * @address:     https://yiyuery.github.io/NoteBooks/
 * @date:        2019/12/9 8:47 下午
 * @email:       [email protected]
 * @description: 本內容僅限於編程技術學習使用,轉發請註明出處.
 */
package com.example.spring.aop.service.impl;

import com.example.spring.aop.service.IPersonManagerService;
import com.example.spring.aop.simple.BusinessLogMonitor;
import lombok.extern.slf4j.Slf4j;

/**
 * <p>
 *
 * </p>
 *
 * @author xiazhaoyang
 * @version V1.0.0
 * @date 2019/12/9 8:47 下午
 * @modificationHistory=========================邏輯或功能性重大變更記錄
 * @modify By: {修改人}  2019/12/9
 * @modify reason: {方法名}:{原因}
 * ...
 */
@Slf4j
public class PersonManagerServiceImpl implements IPersonManagerService {

    @Override
    public void addPerson() {
        BusinessLogMonitor.begin("com.example.spring.aop.service.impl.PersonManagerServiceImpl.addPerson");
        log.info("模擬人員數據添加");
        try {
            Thread.sleep(3000);
        } catch (Exception e) {
            log.error("PersonManagerServiceImpl addPerson failed!");
        }
        BusinessLogMonitor.end();
    }
}


輸出測試

20:54:01.621 [main] INFO com.example.spring.aop.simple.BusinessLogMonitor - begin monitor...
20:54:01.626 [main] INFO com.example.spring.aop.service.impl.PersonManagerServiceImpl - 模擬人員數據添加
20:54:04.631 [main] INFO com.example.spring.aop.simple.BusinessLogMonitor - end monitor....
20:54:04.632 [main] INFO com.example.spring.aop.simple.BusinessLogHandler - com.example.spring.aop.service.impl.PersonManagerServiceImpl.addPerson執行耗時3006毫秒

問題很明顯,如果所有的業務方法都加上這兩行,無疑是件很恐怖的事情。那麼AOP就是爲了解決這個問題的。

 BusinessLogMonitor.begin("com.example.spring.aop.service.impl.PersonManagerServiceImpl.addPerson");
//....
BusinessLogMonitor.end();

在方法的執行前後,填充增強邏輯。這也就是我們常看到的"橫切"、"織入"操作。

JDK 動態代理實現橫切邏輯

先來補充下基本知識

JDK動態代理主要涉及java.lang.reflect包中的兩個類:

  • Proxy 利用接口InvocationHandler動態創建一個符合某一接口定義的實例,生成目標類的代理對象。

  • InvocationHandler 接口,可以通過該接口實現橫切邏輯,並通過反射機制調用目標類的代碼,動態地將橫切邏輯和業務邏輯編織在一起。

接下來,我們嘗試利用JDK的動態代理來替代下面的邏輯:

BusinessLogMonitor.begin("com.example.spring.aop.service.impl.PersonManagerServiceImpl.addPerson");
//....
BusinessLogMonitor.end();

首先,定義接口InvocationHandler的實現類,用來充當代理類的實現:

/*
 * @ProjectName: 編程學習
 * @Copyright:   2019 HangZhou xiazhaoyang Dev, Ltd. All Right Reserved.
 * @address:     https://yiyuery.github.io/NoteBooks/
 * @date:        2019/12/9 10:07 下午
 * @email:       [email protected]
 * @description: 本內容僅限於編程技術學習使用,轉發請註明出處.
 */
package com.example.spring.aop.jdk;

import com.example.spring.aop.simple.BusinessLogMonitor;
import lombok.Setter;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * <p>
 *
 * </p>
 *
 * @author xiazhaoyang
 * @version V1.0.0
 * @date 2019/12/9 10:07 下午
 * @modificationHistory=========================邏輯或功能性重大變更記錄
 * @modify By: {修改人}  2019/12/9
 * @modify reason: {方法名}:{原因}
 * ...
 */
@Setter
public class BusinessLogInvocationHandler implements InvocationHandler {

    private Object target;

    public BusinessLogInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        BusinessLogMonitor.begin(target.getClass().getName()+"."+method.getName());
        Object obj = method.invoke(target,args);
        BusinessLogMonitor.end();
        return obj;
    }
}

補充定義個人員刪除的方法來測試JDK動態代理的效果

@Override
public void deletePerson() {
    log.info("模擬人員數據刪除");
    try {
        Thread.sleep(3000);
    } catch (Exception e) {
        log.error("PersonManagerServiceImpl deletePerson failed!");
    }
}

測試輸出

/**
 * 通過JDK代理執行業務方法
 */
@Test
public void deletePersonWithInvocationHandler() {
    PersonManagerServiceImpl personManagerService = new PersonManagerServiceImpl();
    BusinessLogInvocationHandler businessLogInvocationHandler = new BusinessLogInvocationHandler(personManagerService);
    //創建代理實例
    IPersonManagerService proxy = (IPersonManagerService)Proxy.newProxyInstance(personManagerService.getClass().getClassLoader(),
            personManagerService.getClass().getInterfaces(), businessLogInvocationHandler);
    //調用代理實例
    proxy.deletePerson();
}
//22:17:48.012 [main] INFO com.example.spring.aop.simple.BusinessLogMonitor - begin monitor...
//22:17:48.015 [main] INFO com.example.spring.aop.service.impl.PersonManagerServiceImpl - 模擬人員數據刪除
//22:17:51.020 [main] INFO com.example.spring.aop.simple.BusinessLogMonitor - end monitor....
//22:17:51.021 [main] INFO com.example.spring.aop.simple.BusinessLogHandler - com.example.spring.aop.service.impl.PersonManagerServiceImpl.deletePerson執行耗時3005毫秒

可以比對下兩次,的確實現了一樣的功能。

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

newProxyInstance方法有三個入參,第一個是類加載器,第二個是需要代理實例實現的接口列表。

在這裏插入圖片描述

在這裏插入圖片描述

即要使用JDK的動態代理,需要定義需要實現的接口,代理類實際是將該接口實現了一遍,並在增強邏輯插入後,通過invoke方法調用被代理類的方法中的業務邏輯。

沒有實現接口的對象調用自身getClass().getInterfaces()返回的接口信息是空的,無法使用JDK動態代理。

那麼,如何在不定義多餘接口的情況下,直接是實現代理實例的創建呢?CGLib給出了份完美的答案。

CGLib 動態代理實現橫切邏輯

先引入下相關依賴

在這裏插入圖片描述

dependencies {
    //...
    compile ('cglib:cglib:3.3.0')
    //...
}

首先實現接口MethodInterceptor,傳入被代理對象的實例類型,並對暴露獲取代理類實例的方法。

/*
 * @ProjectName: 編程學習
 * @Copyright:   2019 HangZhou xiazhaoyang Dev, Ltd. All Right Reserved.
 * @address:     https://yiyuery.github.io/NoteBooks/
 * @date:        2019/12/9 10:43 下午
 * @email:       [email protected]
 * @description: 本內容僅限於編程技術學習使用,轉發請註明出處.
 */
package com.example.spring.aop.cglib;

import com.example.spring.aop.simple.BusinessLogMonitor;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * <p>
 *
 * </p>
 *
 * @author xiazhaoyang
 * @version V1.0.0
 * @date 2019/12/9 10:43 下午
 * @modificationHistory=========================邏輯或功能性重大變更記錄
 * @modify By: {修改人}  2019/12/9
 * @modify reason: {方法名}:{原因}
 * ...
 */
public class CglibProxy implements MethodInterceptor {

    private Enhancer enhancer = new Enhancer();

    /**
     * 創建代理類
     * @param clazz
     * @return
     */
    public Object createProxy(Class clazz){
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        BusinessLogMonitor.begin(obj.getClass().getName()+"."+method.getName());
        Object result = proxy.invokeSuper(obj, args);
        BusinessLogMonitor.end();
        return result;
    }
}


測試輸出

/**
 * 通過CGLib代理執行業務方法
 */
@Test
public void deletePersonWithCglibProxy() {
    CglibProxy proxy = new CglibProxy();
    //創建代理類
    PersonManagerServiceImpl personManagerServiceProxy = (PersonManagerServiceImpl)proxy.createProxy(PersonManagerServiceImpl.class);
    //調用代理實例
    personManagerServiceProxy.deletePerson();
}
//22:57:53.103 [main] INFO com.example.spring.aop.simple.BusinessLogMonitor - begin monitor...
//22:57:53.117 [main] INFO com.example.spring.aop.service.impl.PersonManagerServiceImpl - 模擬人員數據刪除
//22:57:56.119 [main] INFO com.example.spring.aop.simple.BusinessLogMonitor - end monitor....
//22:57:56.120 [main] INFO com.example.spring.aop.simple.BusinessLogHandler - com.example.spring.aop.service.impl.PersonManagerServiceImpl$$EnhancerByCGLIB$$77eaf4f6.deletePerson執行耗時3014毫秒

兩點需要注意:

  • 功能上也實現了之前JDK動態代理的橫切邏輯
  • 但是在CGLib代理輸出的結果中可以看到PersonManagerServiceImpl$$EnhancerByCGLIB$$77eaf4f6,這個和之前JDK動態代理實例輸出的結果PersonManagerServiceImpl有所差異。

這個帶有CGLIB關鍵信息的實例其實是Cglib對象爲PersonManagerServiceImpl動態創建的一個織入業務日誌輸出邏輯的代理對象,並調用該代理類的業務方法。該對象EnhancerByCGLIB$$77eaf4f6PersonManagerServiceImpl的一個之類。

因此,由於CGLib採用動態創建之類的方式生成代理對象,所以不能對目標類的final或是private方法進行代理。
而且子類實現增強邏輯,並在增強邏輯調用的中間,調用被代理類(父類)的方法。

核心源碼

//step-1 CglibProxy
private Enhancer enhancer = new Enhancer();

/**
    * 創建代理類
    * @param clazz
    * @return
    */
public Object createProxy(Class clazz){
    enhancer.setSuperclass(clazz);
    enhancer.setCallback(this);
    return enhancer.create();
}

//step-2 Enhancer
private Object createHelper() {
    preValidate();
    Object key = KEY_FACTORY.newInstance((superclass != null) ? superclass.getName() : null,
            ReflectUtils.getNames(interfaces),
            filter == ALL_ZERO ? null : new WeakCacheKey<CallbackFilter>(filter),
            callbackTypes,
            useFactory,
            interceptDuringConstruction,
            serialVersionUID);
    this.currentKey = key;
    Object result = super.create(key);
    return result;
}

在這裏插入圖片描述

//step-3  AbstractClassGenerator 構造子類
protected Object create(Object key) {
    try {
        ClassLoader loader = getClassLoader();
        Map<ClassLoader, ClassLoaderData> cache = CACHE;
        ClassLoaderData data = cache.get(loader);
        if (data == null) {
            synchronized (AbstractClassGenerator.class) {
                cache = CACHE;
                data = cache.get(loader);
                if (data == null) {
                    Map<ClassLoader, ClassLoaderData> newCache = new WeakHashMap<ClassLoader, ClassLoaderData>(cache);
                    data = new ClassLoaderData(loader);
                    newCache.put(loader, data);
                    CACHE = newCache;
                }
            }
        }
        this.key = key;
        Object obj = data.get(this, getUseCache());
        if (obj instanceof Class) {
            return firstInstance((Class) obj);
        }
        return nextInstance(obj);
    } catch (RuntimeException e) {
        throw e;
    } catch (Error e) {
        throw e;
    } catch (Exception e) {
        throw new CodeGenerationException(e);
    }
}

//step-4 Enhancer
protected Object nextInstance(Object instance) {
    EnhancerFactoryData data = (EnhancerFactoryData) instance;

    if (classOnly) {
        return data.generatedClass;
    }

    Class[] argumentTypes = this.argumentTypes;
    Object[] arguments = this.arguments;
    if (argumentTypes == null) {
        argumentTypes = Constants.EMPTY_CLASS_ARRAY;
        arguments = null;
    }
    return data.newInstance(argumentTypes, arguments, callbacks);
}

public Object newInstance(Class[] argumentTypes, Object[] arguments, Callback[] callbacks) {
    setThreadCallbacks(callbacks);
    try {
        // Explicit reference equality is added here just in case Arrays.equals does not have one
        if (primaryConstructorArgTypes == argumentTypes ||
                Arrays.equals(primaryConstructorArgTypes, argumentTypes)) {
            // If we have relevant Constructor instance at hand, just call it
            // This skips "get constructors" machinery
            return ReflectUtils.newInstance(primaryConstructor, arguments);
        }
        // Take a slow path if observing unexpected argument types
        return ReflectUtils.newInstance(generatedClass, argumentTypes, arguments);
    } finally {
        // clear thread callbacks to allow them to be gc'd
        setThreadCallbacks(null);
    }

}

在這裏插入圖片描述

總結

Spring AOP 其實也是利用JDK或是CGLib動態代理技術爲目標bean實現目標Bean織入橫切邏輯的。本文就底層使用幾個技術,分別實現了業務日誌輸出通過代理織入業務邏輯。但是仍然有需要改進的地方:

  • 目標類都加入了橫切邏輯,每個都需要單獨調用CglibProxy來構造代理類,我們往往希望可以在某些特定的方法中實現橫切邏輯
  • 通過硬編碼實現織入橫切邏輯的織入點,不夠靈活
  • 手工編寫代理實例的創建過程,在爲不同類創建代理時,需要編寫大量冗餘代碼,不夠通用。

這三個問題在AOP中佔有很重要的地位,下一篇文章將開始講述Spring AOP是如何優雅的解決這三個問題的。

To be continue.....


更多

掃碼關注架構探險之道,回覆文章標題,獲取本文相關源碼和資源鏈接

在這裏插入圖片描述

知識星球(掃碼加入獲取歷史源碼和文章資源鏈接)

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章