手機用戶請
橫屏
獲取最佳閱讀體驗,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個工作:
- 如何通過切點和增強定位到連接點?
- 如何在增強中編寫切面的代碼?
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$$77eaf4f6
是PersonManagerServiceImpl
的一個之類。
因此,由於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.....
更多
掃碼關注
架構探險之道
,回覆文章標題,獲取本文相關源碼和資源鏈接
知識星球(掃碼加入獲取歷史源碼和文章資源鏈接)