Android面試中,關于 Handler 的問題是必備的,但是這些關于 Handler 的知識點你都知道嗎?
代碼分析基于 Android SDK 28
大家可以先看上面的問題思考一下,如果都清楚的話,下面的文章也沒必要看了~
關于 Handler 的原理,相比不用多說了,大家都應該知道,一張圖就可以說明(圖片來自網絡)。
除了上面 Handler 的基本原理,子線程中如何使用 Handler 也是一個常見的問題。
子線程中使用 Handler 需要先執行兩個操作:Looper.prepare 和 Looper.loop。
為什么需要這樣做呢?Looper.prepare 和 Looper.loop 都做了什么事情呢?
我們知道如果在子線程中直接創建一個 Handler 的話,會報如下的錯誤:
"Can't create handler inside thread xxx that has not called Looper.prepare()
我們可以看一下 Handler 的構造函數,里面會對 Looper 進行判斷,如果通過 ThreadLocal 獲取的 Looper 為空,則報上面的錯誤。
public Handler(Callback callback, boolean async) {
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
那么 Looper.prepare 里做了什么事情呢?
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
可以看到,Looper.prepare 就是創建了 Looper 并設置給 ThreadLocal,這里的一個細節是每個 Thread 只能有一個 Looper,否則也會拋出異常。
而 Looper.loop 就是開始讀取 MessageQueue 中的消息,進行執行了。
這里一般會引申一個問題,就是主線程中為什么不用手動調用這兩個方法呢?相信大家也都明白,就是 ActivityThread.main 中已經進行了調用。
通過這個問題,又可以引申到 ActivityThread 相關的知識,這里就不細說了。
上面說到 Looper.loop 其實就是開始讀取 MessageQueue 中的消息了,那 MessageQueue 中沒有消息的時候,Looper 在做什么呢?我們知道是在等待消息,那是怎么等待的呢?
通過 Looper.loop 方法,我們知道是 MessageQueue.next() 來獲取消息的,如果沒有消息,那就會阻塞在這里,MessageQueue.next 是怎么等待的呢?
public static void loop() {
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
}
}
Message next() {
for (;;) {
nativePollOnce(ptr, nextPollTimeoutMillis);
// ...
}
}
在 MessageQueue.next 里調用了 native 方法 nativePollOnce。
// android_os_MessageQueue.cpp
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
jlong ptr, jint timeoutMillis) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
// ...
mLooper->pollOnce(timeoutMillis);
// ...
}
// Looper.cpp
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
// ...
result = pollInner(timeoutMillis);
// ...
}
int Looper::pollInner(int timeoutMillis) {
// ...
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
}
從上面代碼中我們可以看到,在 native 側,最終是使用了 epoll_wait 來進行等待的。
這里的 epoll_wait 是 Linux 中 epoll 機制中的一環,關于 epoll 機制這里就不進行過多介紹了,大家有興趣可以參考 segmentfault.com/a/119000000…
那其實說到這里,又有一個問題,為什么不用 java 中的 wait / notify 而是要用 native 的 epoll 機制呢?
說起來 java 中的 wait / notify 也能實現阻塞等待消息的功能,在 Android 2.2 及以前,也確實是這樣做的。
可以參考這個 commit www.androidos.net.cn/android/2.1…
那為什么后面要改成使用 epoll 呢?通過看 commit 記錄,是需要處理 native 側的事件,所以只使用 java 的 wait / notify 就不夠用了。
具體的改動就是這個 commit android.googlesource.com/platform/fr…
Sketch of Native input for MessageQueue / Looper / ViewRoot
MessageQueue now uses a socket for internal signalling, and is prepared
to also handle any number of event input pipes, once the plumbing is
set up with ViewRoot / Looper to tell it about them as appropriate.
Change-Id: If9eda174a6c26887dc51b12b14b390e724e73ab3
不過這里最開始使用的還是 select,后面才改成 epoll。
具體可見這個 commit android.googlesource.com/platform/fr…
至于 select 和 epoll 的區別,這里也不細說了,大家可以在上面的參考文章中一起看看。
這里的關系是一個線程對應一個 Looper 對應一個 MessageQueue 對應多個 Handler。
既然一個線程對應一個 MessageQueue,那多個線程給 MessageQueue 發消息時是如何保證線程安全的呢?
說來簡單,就是加了個鎖而已。
// MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
// ...
}
}
Handler 引申的另一個問題就是延遲消息在 Handler 中是怎么處理的?定時器還是其他方法?
這里我們先從事件發起開始看起:
// Handler.java
public final boolean postDelayed(Runnable r, long delayMillis)
{
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
// 傳入的 time 是 uptimeMillis + delayMillis
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
// ...
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
// 調用 MessageQueue.enqueueMessage
return queue.enqueueMessage(msg, uptimeMillis);
}
從上面的代碼邏輯來看,Handler post 消息以后,一直調用到 MessageQueue.enqueueMessage 里,其中最重要的一步操作就是傳入的時間是 uptimeMillis + delayMillis。
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
// ...
msg.when = when;
Message p = mMessages; // 下一條消息
// 根據 when 進行順序排序,將消息插入到其中
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// 找到 合適的節點
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
}
// 插入操作
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// 喚醒隊列進行取消息
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
通過上面代碼我們看到,post 一個延遲消息時,在 MessageQueue 中會根據 when 的時長進行一個順序排序。
接著我們再看看怎么使用 when 的。
Message next() {
// ...
for (;;) {
// 通過 epoll_wait 等待消息,等待 nextPollTimeoutMillis 時長
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// 當前時間
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// 獲得一個有效的消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) { // 說明需要延遲執行,通過; nativePollOnce 的 timeout 來進行延遲
// 獲取需要等待執行的時間
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else { // 立即執行的消息,直接返回
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
// 當前沒有消息要執行,則執行 IdleHandler 中的內容
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// 如果沒有 IdleHandler 需要執行,則去等待 消息的執行
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 執行 idle handlers 內容
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// 如果執行了 idle handlers 的內容,現在消息可能已經到了執行時間,所以這個時候就不等待了,再去檢查一下消息是否可以執行, nextPollTimeoutMillis 需要置為 0
nextPollTimeoutMillis = 0;
}
}
通過上面的代碼分析,我們知道了執行 Handler.postDelayd 時候,會執行下面幾個步驟:
我們最常用的 Handler 功能就是 Handler.post,除此之外,還有 View.post 也經常會用到,那么這兩個有什么區別呢?
我們先看下 View.post 的代碼。
// View.java
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}
通過代碼來看,如果 AttachInfo 不為空,則通過 handler 去執行,如果 handler 為空,則通過 RunQueue 去執行。
那我們先看看這里的 AttachInfo 是什么。
這個就需要追溯到 ViewRootImpl 的流程里了,我們先看下面這段代碼。
// ViewRootImpl.java
final ViewRootHandler mHandler = new ViewRootHandler();
public ViewRootImpl(Context context, Display display) {
// ...
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
context);
}
private void performTraversals() {
final View host = mView;
// ...
if (mFirst) {
host.dispatchAttachedToWindow(mAttachInfo, 0);
mFirst = false;
}
// ...
}
代碼寫了一些關鍵部分,在 ViewRootImpl 構造函數里,創建了 mAttachInfo,然后在 performTraversals 里,如果 mFirst 為 true,則調用 host.dispatchAttachedToWindow,這里的 host 就是 DecorView。
這里還有一個知識點就是 mAttachInfo 中的 mHandler 其實是 ViewRootImpl 內部的 ViewRootHandler。
然后就調用到了 DecorView.dispatchAttachedToWindow,其實就是 ViewGroup 的 dispatchAttachedToWindow,一般 ViewGroup 中相關的方法,都是去依次調用 child 的對應方法,這個也不例外,依次調用子 View 的 dispatchAttachedToWindow,把 AttachInfo 傳進去,在 子 View 中給 mAttachInfo 賦值。
// ViewGroup
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
super.dispatchAttachedToWindow(info, visibility);
mGroupFlags &= ~FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
final View child = children[i];
child.dispatchAttachedToWindow(info,
combineVisibility(visibility, child.getVisibility()));
}
final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
for (int i = 0; i < transientCount; ++i) {
View view = mTransientViews.get(i);
view.dispatchAttachedToWindow(info,
combineVisibility(visibility, view.getVisibility()));
}
}
// View
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
// ...
}
看到這里,大家可能忘記我們開始剛剛要做什么了。
我們是在看 View.post 的流程,再回顧一下 View.post 的代碼:
// View.java
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
getRunQueue().post(action);
return true;
}
現在我們知道 attachInfo 是什么了,是 ViewRootImpl 首次觸發 performTraversals 傳進來的,也就是觸發 performTraversals 之后,View.post 都是通過 ViewRootImpl 內部的 Handler 進行處理的。
如果在 performTraversals 之前或者 mAttachInfo 置為空以后進行執行,則通過 RunQueue 進行處理。
那我們再看看 getRunQueue().post(action); 做了些什么事情。
這里的 RunQueue 其實是 HandlerActionQueue。
HandlerActionQueue 的代碼看一下。
public class HandlerActionQueue {
public void post(Runnable action) {
postDelayed(action, 0);
}
public void postDelayed(Runnable action, long delayMillis) {
final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
synchronized (this) {
if (mActions == null) {
mActions = new HandlerAction[4];
}
mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
mCount++;
}
}
public void executeActions(Handler handler) {
synchronized (this) {
final HandlerAction[] actions = mActions;
for (int i = 0, count = mCount; i < count; i++) {
final HandlerAction handlerAction = actions[i];
handler.postDelayed(handlerAction.action, handlerAction.delay);
}
mActions = null;
mCount = 0;
}
}
}
通過上面的代碼我們可以看到,執行 getRunQueue().post(action); 其實是將代碼添加到 mActions 進行保存,然后在 executeActions 的時候進行執行。
executeActions 執行的時機只有一個,就是在 dispatchAttachedToWindow(AttachInfo info, int visibility) 里面調用的。
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
}
看到這里我們就知道了,View.post 和 Handler.post 的區別就是:
這里我們又可以回答一個問題了,就是為什么 View.post 里可以拿到 View 的寬高信息呢?
因為 View.post 的 Runnable 執行的時候,已經執行過 performTraversals 了,也就是 View 的 measure layout draw 方法都執行過了,自然可以獲取到 View 的寬高信息了。
這個問題就是老生常談了,可以由此再引申出內存泄漏的知識點,比如:如何排查內存泄漏,如何避免內存泄漏等等。
我們使用 Handler 最多的一個場景就是在非主線程通過 Handler 去操作 主線程的 View。
那么非 UI 線程真的不能操作 View 嗎?
我們在執行 UI 操作的時候,都會調用到 ViewRootImpl 里,以 requestLayout 為例,在 requestLayout 里會通過 checkThread 進行線程的檢查。
// ViewRootImpl.java
public ViewRootImpl(Context context, Display display) {
mThread = Thread.currentThread();
}
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
我們看這里的檢查,其實并不是檢查主線程,是檢查 mThread != Thread.currentThread,而 mThread 指的是 ViewRootImpl 創建的線程。
所以非 UI 線程確實不能操作 View,但是檢查的是創建的線程是否是當前線程,因為 ViewRootImpl 創建是在主線程創建的,所以在非主線程操作 UI 過不了這里的檢查。
一個小小的 Handler,其實可以引申出很多問題,這里這是列舉了一些大家可能忽略的問題,更多的問題就等待大家去探索了~
這里來總結一下:
1. Handler 的基本原理
一張圖解釋(圖片來自網絡)
2. 子線程中怎么使用 Handler
3. MessageQueue 獲取消息是怎么等待
通過 epoll 機制進行等待和喚醒。
4. 為什么不用 wait 而用 epoll 呢?
在 Android 2.2 及之前,使用 Java wait / notify 進行等待,在 2.3 以后,使用 epoll 機制,為了可以同時處理 native 側的消息。
5. 線程和 Handler Looper MessageQueue 的關系
一個線程對應一個 Looper 對應一個 MessageQueue 對應多個 Handler。
6. 多個線程給 MessageQueue 發消息,如何保證線程安全
通過對 MessageQueue 加鎖來保證線程安全。
7. Handler 消息延遲是怎么處理的
8. View.post 和 Handler.post 的區別
View.post 最終也是通過 Handler.post 來執行消息的,執行過程如下:
9. Handler 導致的內存泄漏
略過不講~
10. 非 UI 線程真的不能操作 View 嗎
不能操作,原因是 ViewRootImpl 會檢查創建 ViewRootImpl 的線程和當前操作的線程是否一致。而 ViewRootImpl 是在主線程創建的,所以非主線程不能操作 View。
今天的文章就結束了,希望大家能學到一些不一樣的知識~
其實客戶端開發的知識點就那么多,面試問來問去還是那么點東西。所以面試沒有其他的訣竅,只看你對這些知識點準備的充分程度。so,出去面試時先看看自己復習到了哪個階段就好。
這里再分享一下我面試期間的復習路線:(以下體系的復習資料是我從各路大佬收集整理好的)
《Android開發七大模塊核心知識筆記》
《960全網最全Android開發筆記》
《379頁Android開發面試寶典》
歷時半年,我們整理了這份市面上最全面的安卓面試題解析大全
包含了騰訊、百度、小米、阿里、樂視、美團、58、獵豹、360、新浪、搜狐等一線互聯網公司面試被問到的題目。熟悉本文中列出的知識點會大大增加通過前兩輪技術面試的幾率。
如何使用它?
1.可以通過目錄索引直接翻看需要的知識點,查漏補缺。
2.五角星數表示面試問到的頻率,代表重要推薦指數
《507頁Android開發相關源碼解析》
只要是程序員,不管是Java還是Android,如果不去閱讀源碼,只看API文檔,那就只是停留于皮毛,這對我們知識體系的建立和完備以及實戰技術的提升都是不利的。
真正最能鍛煉能力的便是直接去閱讀源碼,不僅限于閱讀各大系統源碼,還包括各種優秀的開源庫。
資料太多,全部展示會影響篇幅,暫時就先列舉這些部分截圖;
需要的朋友,直接轉發+點贊+私信回復【資料】一鍵領?。。?!
記性不如爛筆頭,今天來分析一下Handler的源碼實現
Handler機制是Android系統的基礎,是多線程之間切換的基礎。下面我們分析一下Handler的源碼實現。
Handler消息機制有4個類合作完成,分別是Handler,MessageQueue,Looper,Message
Handler : 獲取消息,發送消息,以及處理消息的類
MessageQueue:消息隊列,先進先出
Looper : 消息的循環和分發
Message : 消息實體類,分發消息和處理消息的就是這個類
主要工作原理就是:
Looper 類里面有一個無限循環,不停的從MessageQueue隊列中取出消息,然后把消息分發給Handler進行處理
先看看在子線程中發消息,去在主線程中更新,我們就在主線程中打印一句話。
第一步:
在MainActivity中有一個屬性uiHandler,如下:
Handler uiHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if(msg.what == 100){
Log.d("TAG","我是線程1 msg.what=" + msg.what + " msg.obj=" + msg.obj.toString());
}else if(msg.what == 200){
Log.d("TAG","我是線程2 msg.what=" + msg.what + " msg.obj=" + msg.obj.toString());
}
}
};
創建一個Handler實例,重寫了handleMessage方法。根據message中what的標識來區別不同線程發來的數據并打印
第二步:
在按鈕的點擊事件中開2個線程,分別在每個線程中使用 uiHandler獲取消息,并發送消息。如下
findViewById(R.id.tv_hello).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//線程1
new Thread(new Runnable() {
@Override
public void run() {
//1 獲取消息
Message message = uiHandler.obtainMessage();
message.what = 100;
message.obj = "hello,world";
//2 分發消息
uiHandler.sendMessage(message);
}
}).start();
//線程2
new Thread(new Runnable() {
@Override
public void run() {
//1 獲取消息
Message message = uiHandler.obtainMessage();
message.what = 200;
message.obj = "hello,android";
//2 分發消息
uiHandler.sendMessage(message);
}
}).start();
}
});
使用很簡單,兩步就完成了從子線程把數據發送到主線程并在主線程中處理
我們來先分析Handler的源碼
Handler的構造函數
public Handler() {
this(null, false);
}
調用了第兩個參數的構造函數,如下
public Handler(Callback callback, boolean async) {
//FIND_POTENTIAL_LEAKS 為 false, 不走這塊
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
主要是下面幾句:
mLooper = Looper.myLooper(); 調用Looper的靜態方法獲取一個Looper
如果 mLooper == null ,就會拋出異常
Can’t create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()";
說明我們的線程中如果沒有一個looper的話,直接 new Handler() 是會拋出這個異常的。必須首先調用 Looper.prepare(),這個等下講Looper的源碼時就會清楚了。
接下來,把 mLooper中的 mQueue賦值給Handler中的 mQueue,callback是傳出來的值,為null
這樣我們的Handler里面就保存了一個Looper變量,一個MessageQueue消息隊列.
接下來就是 Message message = uiHandler.obtainMessage();
obtainMessage()的源碼如下:
public final Message obtainMessage()
{
//注意傳的是一個 this, 其實就是 Handler本身
return Message.obtain(this);
}
又調用了Message.obtain(this);方法,源碼如下:
public static Message obtain(Handler h) {
//1 調用obtain()獲取一個Message實例m
Message m = obtain();
//2 關鍵的這句,把 h 賦值給了消息的 target,這個target肯定也是Handler了
m.target = h;
//3 返回 m
return m;
}
這樣,獲取的消息里面就保存了 Handler 的實例。
我們隨便看一下 obtain() 方法是如何獲取消息的。如下
public static Message obtain() {
//sPoolSync同步對象用的
synchronized (sPoolSync) {
//sPool是Message類型,靜態變量
if (sPool != null) {
//就是個單鏈表,把表頭返回,sPool再指向下一個
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
//如果sPool為空,則直接 new 一個
return new Message();
}
obtain()獲取消息就是個享元設計模式,享元設計模式用大白話說就是:
池中有,就從池中返回一個,如果沒有,則新創建一個,放入池中,并返回。
使用這種模式可以節省過多的創建對象。復用空閑的對象,節省內存。
最后一句發送消息uiHandler.sendMessage(message);源碼如下:
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
sendMessageDelayed(msg, 0) 源碼如下
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
又調用了sendMessageAtTime() 源碼如下:
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
// Handler中的mQueue,就是前面從Looper.get
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
調用enqueueMessage()
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//注意這句,如果我們發送的消息不是 uiHandler.obtainMessage()獲取的,而是直接 new Message()的,這個時候target為null
//在這里,又把this 給重新賦值給了target了,保證不管怎么獲取的Message,里面的target一定是發送消息的Handler實例
msg.target = this;
// mAsynchronous默認為false,不會走這個
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
最后調用queue.enqueueMessage(msg, uptimeMillis)源碼如下:
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
enqueue單詞的英文意思就是 排隊,入隊的意思。所以enqueueMessage()就是把消息進入插入單鏈表中,上面的源碼可以看出,主要是按照時間的順序把msg插入到由單鏈表中的第一個位置中,接下來我們就需要從消息隊列中取出msg并分了處理了。這時候就調用Looper.loop()方法了。
Looper.loop()的源碼我簡化了一下,把主要的流程留下,方法如下:
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
msg.target.dispatchMessage(msg);
msg.recycleUnchecked();
}
}
可以看到,loop()方法就是在無限循環中不停的從queue中拿出下一個消息
然后調用 msg.target.dispatchMessage(msg) , 上文我們分析過,Message的target保存的就是發送的Handler實例,這我們的這個demo中,就是uiHandler對象。
說白了就是不停的從消息隊列中拿出一個消息,然后發分給Handler的dispatchMessage()方法處理。
Handler的dispatchMessage()方法源碼如下:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
可以看到,一個消息分發給dispatchMessage()之后
1 首先看看消息的callback是否為null,如果不為null,就交給消息的handleCallback()方法處理,如果為null
2 再看看Handler自己的mCallback是否為null,如果不為null,就交給mCallback.handleMessage(msg)進行處理,并且如果返回true,消息就不往下分發了,如果返回false
3 就交給Handler的handleMessage()方法進行處理。
有三層攔截,注意,有好多插件化在攔截替換activity的時候,就是通過反射,把自己實例的Handler實例賦值通過hook賦值給了ActivityThread相關的變量中,并且mCallback不為空,返回了false,這樣不影響系統正常的流程,也能達到攔截的目的。說多了。
前面分析了handler處理消息的機制,也提到了Looper類的作用,下面我們看看Looper的源碼分析
我們知道,APP進程的也就是我們應用的入口是ActivityThread.main()函數。
對這塊不熟悉的需要自己私下補課了。
ActivityThread.main()的源碼同樣經過簡化,如下:
文件位于 /frameworks/base/core/java/android/app/ActivityThread.java
public static void main(String[] args) {
//1 創建一個looper
Looper.prepareMainLooper();
//2 創建一個ActivityThread實例并調用attach()方法
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
//3 消息循環
Looper.loop();
}
可以看到,主線程中第一句就是創建一個looper,并調用了Looper.loop()進行消息循環,因為線程只有有了一個looper,才能消息循環,才能不停的從消息隊列中取出消息,分發消息,并處理消息。沒有消息的時候就阻塞在那,等待消息的到來并處理,這樣的我們的app就是通過這種消息驅動的方式運行起來了。
我們來看下 Looper.prepareMainLooper() 的源碼,如下
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
第一句,調用了prepare(false)
源碼如下:
private static void prepare(boolean quitAllowed) {
//1 查看當前線程中是否有looper存在,有就拋個異常
//這表明,一個線程只能有一個looper存在
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//2 創建一個Looper并存放在sThreadLocal中
sThreadLocal.set(new Looper(quitAllowed));
}
sThreadLocal的定義如下
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
是一個靜態的變量。整個APP進程中只有一個sThreadLocal,sThreadLocal是線程獨有的,每個線程都調用sThreadLocal保存,關于sThreadLocal的原理,其實就是類似HashMap(當然和HashMap是有區別的),也是key,value保存數據,只不過key就是sThreadLocal本身 ,但是映射的數組卻是每個線程中獨有的,這樣就保證了sThreadLocal保存的數據每個線程獨有一份,關于ThreadLocal的源碼分析,后面幾章會講。
既然Looper和線程有關,那么我們來看下Looper類的定義,源碼如下:
/**
* Class used to run a message loop for a thread. Threads by default do
* not have a message loop associated with them; to create one, call
* {@link #prepare} in the thread that is to run the loop, and then
* {@link #loop} to have it process messages until the loop is stopped.
*
* <p>Most interaction with a message loop is through the
* {@link Handler} class.
*
* <p>This is a typical example of the implementation of a Looper thread,
* using the separation of {@link #prepare} and {@link #loop} to create an
* initial Handler to communicate with the Looper.
*
* <pre>
* class LooperThread extends Thread {
* public Handler mHandler;
*
* public void run() {
* Looper.prepare();
*
* mHandler = new Handler() {
* public void handleMessage(Message msg) {
* // process incoming messages here
* }
* };
*
* Looper.loop();
* }
* }</pre>
*/
public final class Looper {
.......
}
我們看上面的注釋
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
這就是經典的Looper的用法 ,可以在一個線程中開始處調用 Looper.prepare();
然后在最后調用 Looper.loop();進行消息循環,可以把其它線程中的Handler實傳進來,這樣,一個Looper線程就有了,可以很方便的切換線程了。
下章節我們來自己設計一個Looper線程,做一些后臺任務。
Handler的消息機制源碼就分析到這了
者 | GitChat
責編 | 郭芮
出品 | CSDN 博客
AOP(Aspect Oriented Programming)面向切面編程是 Spring 框架最核心的組件之一,它通過對程序結構的另一種考慮,補充了 OOP(Object-Oriented Programming)面向對象編程。在 OOP 中模塊化的關鍵單元是類,而在 AOP 中,模塊化單元是切面。也就是說 AOP 關注的不再是類,而是一系列類里面需要共同能力的行為。
本文內容主要包括:
講解 OOP 與 AOP 的簡單對比,以及 AOP 的基礎名詞,比如面試中經常會被問到的 point cut、advice、join point 等。
講解面試經常會被問到的 JDK 動態代理和 CGLIB 代理原理以及區別。
講解 Spring 框架中基于 Schema 的 AOP 實現原理。
講解 Spring 框架中如何基于 AOP 實現的事務管理。
AOP 基礎概念
我們知道在 OOP 中模塊化的關鍵單元是類,類封裝了一類對象的行為和狀態,當多個類有共同的屬性和行為時候我們把這些共同的東西封裝為一個基類,然后多個類可以通過繼承基類的方式來復用這些共同的東西,如果子類需要定制基類行為則可以使用多態。OOP 中使用類來提供封裝,繼承,多態三個特性。
當我們需要在多個不相關的類的某些已有的行為里面添加一個共同的非業務邏輯時候,比如我們需要統計一些業務方法的執行耗時時候,以往做法需要在統計耗時的行為里面寫入計算耗時的代碼,在 OOP 里面這種不涉及業務的散落在多個類的行為里面的代碼叫做橫切(Cross-cutting)代碼,OOP 中這種方式缺點一是業務邏輯行為受到了計算耗時代碼干擾(業務邏輯行為應該只專注業務),缺點二是計算耗時的代碼不能被復用。
而在 AOP 中模塊化單元是切面(Aspect),它將那些影響多個類的共同行為封裝到可重用的模塊中,然后你就可以決定在什么時候對哪些類的哪些行為執行進行攔截(切點),并使用封裝好的可重用模塊里面的行為(通知)對其攔截的業務行為進行功能增強,而不需要修改業務模塊的代碼,切面就是對此的一個抽象描述。
AOP 中有以下基礎概念:
Join point(連接點):程序執行期間的某一個點,例如執行方法或處理異常時候的點。在 Spring AOP 中,連接點總是表示方法的執行。
Advice(通知):通知是指一個切面在特定的連接點要做的事情。通知分為方法執行前通知,方法執行后通知,環繞通知等。許多 AOP 框架(包括 Spring)都將通知建模為攔截器,在連接點周圍維護一系列攔截器(形成攔截器鏈),對連接點的方法進行增強。
Pointcut(切點):一個匹配連接點(Join point)的謂詞表達式。通知(Advice)與切點表達式關聯,并在切點匹配的任何連接點(Join point)(例如,執行具有特定名稱的方法)上運行。切點是匹配連接點(Join point)的表達式的概念,是AOP的核心,并且 Spring 默認使用 AspectJ 作為切入點表達式語言。
Aspect(切面):它是一個跨越多個類的模塊化的關注點,它是通知(Advice)和切點(Pointcut)合起來的抽象,它定義了一個切點(Pointcut)用來匹配連接點(Join point),也就是需要對需要攔截的那些方法進行定義;它定義了一系列的通知(Advice)用來對攔截到的方法進行增強;
Target object(目標對象):被一個或者多個切面(Aspect)通知的對象,也就是需要被 AOP 進行攔截對方法進行增強(使用通知)的對象,也稱為被通知的對象。由于在 AOP 里面使用運行時代理,所以目標對象一直是被代理的對象。
AOP proxy(AOP 代理):為了實現切面(Aspect)功能使用 AOP 框架創建一個對象,在 Spring 框架里面一個 AOP 代理要么指 JDK 動態代理,要么指 CgLIB 代理。
Weaving(織入):是將切面應用到目標對象的過程,這個過程可以是在編譯時(例如使用 AspectJ 編譯器),類加載時,運行時完成。Spring AOP 和其它純 Java AOP 框架一樣,是在運行時執行植入。
Advisor:這個概念是從 Spring 1.2的 AOP 支持中提出的,一個 Advisor 相當于一個小型的切面,不同的是它只有一個通知(Advice),Advisor 在事務管理里面會經常遇到,這個后面會講到。
相比 OOP,AOP 有以下優點:
業務代碼更加簡潔,例如當需要在業務行為前后做一些事情時候,只需要在該行為前后配置切面進行處理,無須修改業務行為代碼。
切面邏輯封裝性好,并且可以被復用,例如我們可以把打日志的邏輯封裝為一個切面,那么我們就可以在多個相關或者不相關的類的多個方法上配置該切面。
JDK 動態代理和 CGLIB 代理原理、區別
在 Spring 中 AOP 代理使用 JDK 動態代理和 CGLIB 代理來實現,默認如果目標對象是接口,則使用 JDK 動態代理,否則使用 CGLIB 來生成代理類,本節就簡單來介紹這兩種代理的原理和區別。
由于 JDK 代理是對接口進行代理,所以首先寫一個接口類:
public interface UserServiceBo {
public int add;
}
然后實現該接口如下:
public class UserServiceImpl implements UserServiceBo {
@Override
public int add {
System.out.println("--------------------add----------------------");
return 0;
}
}
JDK 動態代理是需要實現 InvocationHandler 接口,這里創建一個 InvocationHandler 的實現類:
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
super;
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//(1)
System.out.println("-----------------begin "+method.getName+"-----------------");
//(2)
Object result = method.invoke(target, args);
//(3)
System.out.println("-----------------end "+method.getName+"-----------------");
return result;
}
public Object getProxy{
//(4)
return Proxy.newProxyInstance(Thread.currentThread.getContextClassLoader, target.getClass.getInterfaces, this);
}
}
建立一個測試類:
public static void main(String[] args) {
//(5)打開這個開關,可以把生成的代理類保存到磁盤
System.getProperties.put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//(6)創建目標對象(被代理對象)
UserServiceBo service = new UserServiceImpl;
//(7)創建一個InvocationHandler實例,并傳遞被代理對象
MyInvocationHandler handler = new MyInvocationHandler(service);
//(8)生成代理類
UserServiceBo proxy = (UserServiceBo) handler.getProxy;
proxy.add;
}
其中代碼(6)創建了一個 UserServiceImpl 的實例對象,這個對象就是要被代理的目標對象。
代碼(7)創建了一個 InvocationHandler 實例,并傳遞被代理目標對象 service 給內部變量 target。
代碼(8)調用 MyInvocationHandler 的 getProxy 方法使用 JDK 生成一個代理對象。
代碼(5)設置系統屬性變量 sun.misc.ProxyGenerator.saveGeneratedFiles 為 true,這是為了讓代碼(8)生成的代理對象的字節碼文件保存到磁盤。
運行上面代碼會在項目的 com.sun.proxy 下面會生成 $Proxy0.class 類,經反編譯后核心 Java 代碼如下:
package com.sun.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.JDK.UserServiceBo;
public final class $Proxy0
extends Proxy
implements UserServiceBo
{
private static Method m1;
private static Method m3;
private static Method m0;
private static Method m2;
public $Proxy0(InvocationHandler paramInvocationHandler)
{
super(paramInvocationHandler);
}
public final int add
{
try
{
//(9)第一個參數是代理類本身,第二個是實現類的方法,第三個是參數
return h.invoke(this, m3, );
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
...
static
{
try
{
m3 = Class.forName("proxy.JDK.UserServiceBo").getMethod("add", new Class[0]);
...
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
...
}
}
代理類 $Proxy0
中代碼(9)中的 h 就是 main 函數里面創建的 MyInvocationHandler 的實例,h.invoke(this, m3, ) 實際就是調用的 MyInvocationHandler 的 invoke 方法,而后者則是委托給被代理對象進行執行,這里可以對目標對象方法進行攔截,然后對其功能進行增強。
另外代碼(8)生成的 proxy 對象實際就是 $Proxy0
的一個實例,當調用 proxy.add 時候,實際是調用的代理類$Proxy0
的 add 方法,后者內部則委托給 MyInvocationHandler 的 invoke 方法,invoke 方法內部有調用了目標對象 service 的 add 方法。
那么接口(UserServiceBo)、目標對象(被代理對象 UserServiceImpl)、代理對象($Proxy0
)三者具體關系可以使用下圖表示:
enter image description here
可知 JDK 動態代理是對接口進行的代理;代理類實現了接口,并繼承了 Proxy 類;目標對象與代理對象沒有什么直接關系,只是它們都實現了接口,并且代理對象執行方法時候內部最終是委托目標對象執行具體的方法。
相比 JDK 動態代理對接口進行代理,CGLIB 則是對實現類進行代理,這意味著無論目標對象是否有接口,都可以使用 CGLIB 進行代理。
下面結合一個使用 CGLIB 對實現類進行動態代理的簡單代碼來講解。
使用 CGLIB 進行代理需要實現 MethodInterceptor,創建一個方法攔截器 CglibProxy 類:
public class CglibProxy implements MethodInterceptor {
//(10)
private Enhancer enhancer = new Enhancer;
//(11)
public Object getProxy(Class clazz) {
//(12)設置被代理類的Class對象
enhancer.setSuperclass(clazz);
//(13)設置攔截器回調
enhancer.setCallback( this);
return enhancer.create;
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println(obj.getClass.getName+"."+method.getName);
Object result = proxy.invokeSuper(obj, args);
return result;
}
}
public void testCglibProxy {
//(14)生成代理類到本地
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/zhuizhumengxiang/Downloads");
//(15)生成目標對象
UserServiceImpl service = new UserServiceImpl;
//(16)創建CglibProxy對象
CglibProxy cp = new CglibProxy;
//(17)生成代理類
UserServiceBo proxy = (UserServiceBo) cp.getProxy(service.getClass);
proxy.add;
}
執行上面代碼會在 /Users/zhuizhumengxiang/Downloads
目錄生成代理類UserServiceImpl$$EnhancerByCGLIB$$d0bce05a.class
文件,反編譯后部分代碼如下:
public class UserServiceImpl$$EnhancerByCGLIB$$d0bce05a extends UserServiceImpl
implements Factory
{
static void CGLIB$STATICHOOK1
{
//(18)空參數
CGLIB$emptyArgs = new Object[0];
//(19)獲取UserServiceImpl的add方法列表
Method tmp191_188 = ReflectUtils.findMethods(new String { "add", "I" }, (localClass2 = Class.forName("zlx.cglib.zlx.UserServiceImpl")).getDeclaredMethods);
CGLIB$add>CGLIB$add$0$Method = tmp191_188[0];<$Method = tmp191_188[0];
//(20)創建CGLIB$add>//(20)創建CGLIB$add$0,根據創建一個MethodProxy<,根據創建一個MethodProxy
CGLIB$add>CGLIB$add$0$Proxy = MethodProxy.create(localClass2, localClass1, "I", "add", "CGLIB$add$0");<$Proxy = MethodProxy.create(localClass2, localClass1, "I", "add", "CGLIB$add>CGLIB$add$0$Proxy = MethodProxy.create(localClass2, localClass1, "I", "add", "CGLIB$add$0");<");
}
static
{
CGLIB$STATICHOOK1;
}
//(21)
final int CGLIB$add>final int CGLIB$add$0<
{
return super.add;
}
//(22)
public final int add
{
...
//(23)獲取攔截器,這里為CglibProxy的實例
MethodInterceptor tmp17_14 = this.CGLIB$CALLBACK_0;
if (tmp17_14 != )
{ //(24)調用攔截器的intercept方法
Object tmp36_31 = tmp17_14.intercept(this, CGLIB$add>, CGLIB$add$0$Method, CGLIB$emptyArgs, CGLIB$add$0$Proxy);<$Method, CGLIB$emptyArgs, CGLIB$add>, CGLIB$add$0$Method, CGLIB$emptyArgs, CGLIB$add$0$Proxy);<$Proxy);
tmp36_31;
return tmp36_31 == ? 0 : ((Number)tmp36_31).intValue;
}
return super.add;
}
}
代碼(18)創建了一個 CGLIB$emptyArgs
,因為 add 方法是無入參的,所以這里創建的 Object 對象個數為0,這對象是 CglibProxy 攔截器的 intercept 的第三個參數。
代碼(19)獲取 UserServiceImpl 的 add 方法列表,并把唯一方法賦值給 CGLIB$add>CGLIB$add$0$Method<$Method
,這個對象是 CglibProxy 攔截器的 intercept 第二個參數。
代碼(20)創建了一個 MethodProxy 對象,當調用 MethodProxy 對象的 invokeSuper 方法時候會直接調用代理對象的 CGLIB$add>CGLIB$add$0<
方法,也就是直接調用父類 UserServiceImpl 的 add 方法,這避免了一次反射調用,創建的 MethodProxy 對象是 CglibProxy 攔截器的 intercept 的第四個參數。
代碼(22)是代理類的 add 方法,代碼(17)生成的代理對象 proxy 就是 UserServiceImpl$$EnhancerByCGLIB$$d0bce05a
的一個實例,當調用 proxy.add 方法時候就是調用的代碼(22),從代碼(22)可知內部調用了攔截器 CglibProxy 的 intercept 方法,可知 intercept 的第一個參數就是代理對象本身。
那么接口(UserServiceBo)、目標對象(被代理對象 UserServiceImpl),代理對象(UserServiceImpl$$EnhancerByCGLIB$$d0bce05a
)三者具體關系可以使用下圖表示:
enter image description here
可知接口和代理對象沒有啥關系,代理對象是繼承了目標對象和實現了 Factory 接口。
JDK 動態代理機制只能對接口進行代理,其原理是動態生成一個代理類,這個代理類實現了目標對象的接口,目標對象和代理類都實現了接口,但是目標對象和代理類的 Class 對象是不一樣的,所以兩者是沒法相互賦值的。
CGLIB 是對目標對象本身進行代理,所以無論目標對象是否有接口,都可以對目標對象進行代理,其原理是使用字節碼生成工具在內存生成一個繼承目標對象的代理類,然后創建代理對象實例。由于代理類的父類是目標對象,所以代理類是可以賦值給目標對象的,自然如果目標對象有接口,代理對象也是可以賦值給接口的。
CGLIB 動態代理中生成的代理類的字節碼相比 JDK 來說更加復雜。
JDK 使用反射機制調用目標類的方法,CGLIB 則使用類似索引的方式直接調用目標類方法,所以 JDK 動態代理生成代理類的速度相比 CGLIB 要快一些,但是運行速度比 CGLIB 低一些,并且 JDK 代理只能對有接口的目標對象進行代理。
Spring 框架中基于 Schema 的 AOP 實現原理
Spring 提供了兩種方式對 AOP 進行支持:基于 Schema 的 AOP,基于注解的 AOP。
基于 Schema 的 AOP 允許您基于 XML 的格式配置切面功能,Spring 2.0 提供了新的“aop”命名空間標記來定義切面的支持,基于注解的 AOP 則允許您使用 @Aspect 風格來配置切面。
本文就來先講講基于 Schema 的 AOP 的實現原理。
一個切面實際上是一個被定義在 Spring application context 里面的一個正常的 Java 對象,配置切面對目標對象進行增強時候,一般使用下面配置格式:
<!--(1) -->
<bean id="helloService" class="zlx.test.aop.HelloServiceBoImpl" />
<!--(2) -->
<bean id="myAspect" class="zlx.test.aop.MyAspect" />
<aop:config>
<!--(3) -->
<aop:pointcut id="pointcut"
expression="execution(* *..*BoImpl.sayHello(..))"/>
<!--(4) -->
<aop:aspect ref="myAspect">
<!--(4.1) -->
<aop:before pointcut-ref="pointcut" method="beforeAdvice" />
<!--(4.2) -->
<aop:after pointcut="execution(* *..*BoImpl.sayHello(..))"
method="afterAdvice" />
<!--(4.3) -->
<aop:after-returning
pointcut="execution(* *..*BoImpl.sayHelloAfterReturn(..))" method="afterReturningAdvice"
arg-names="content" returning="content" />
<!--(4.4) -->
<aop:after-throwing pointcut="execution(* *..*BoImpl.sayHelloThrowExecption(..))"
method="afterThrowingAdvice" arg-names="e" throwing="e" />
<!--(4.5) -->
<aop:around pointcut="execution(* *..*BoImpl.sayHelloAround(..))"
method="aroundAdvice" />
</aop:aspect>
</aop:config>
<!--(5) -->
<bean id = "tracingInterceptor" class="zlx.test.aop.TracingInterceptor"/>
<!--(6) -->
<aop:config>
<aop:pointcut id="pointcutForadVisor" expression="execution(* *..*BoImpl.sayHelloAdvisor(..))" />
<aop:advisor pointcut-ref="pointcutForadVisor" advice-ref="tracingInterceptor" />
</aop:config>
代碼(1)創建一個要被 AOP 進行功能增強的目標對象(Target object),HelloServiceBoImpl 的代碼如下:
public class HelloServiceBoImpl implements HelloServiceBo{
@Override
public void sayHello(String content) {
System.out.println("sayHello:" + content);
}
@Override
public String sayHelloAround(String content) {
System.out.println(" sayHelloAround:" + content);
return content;
}
@Override
public String sayHelloAfterReturn(String content) {
System.out.println("sayHelloAround:" + content);
return content;
}
@Override
public void sayHelloThrowExecption {
System.out.println("sayHelloThrowExecption");
throw new RuntimeException("hello Im an exception");
}
}
public interface HelloServiceBo {
public void sayHello(String content);
public String sayHelloAround(String content);
public String sayHelloAfterReturn(String content);
public void sayHelloThrowExecption;
}
代碼(2)實質是定義了切面要使用的一系列的通知方法(Advice),用來對目標對象(Target object)的方法進行增強,MyAspect 的代碼如下:
public class MyAspect {
public void beforeAdvice(String content) {
System.out.println("---before advice "+ "---");
}
public void afterAdvice(JoinPoint jp) {
System.out.println("---after advice " + jp.getArgs[0].toString+"---");
}
public Object afterReturningAdvice(Object value) {
System.out.println("---afterReturning advice " + value+"---");
return value + " ha";
}
public void afterThrowingAdvice(Exception e) {
System.out.println("---after throwing advice exception:" + e+"---");
}
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
Object obj = pjp.getArgs;
String content = (String) obj[0];
System.out.println("---before sayHelloAround execute---");
String retVal = (String) pjp.proceed;
System.out.println("---after sayHelloAround execute---");
return retVal+ " suffix";
}
}
代碼(3)定義了一個切點(pointcut),這里是對滿足* *..*BoImpl
表達式的類里面的方法名稱為 sayHello 的方法進行攔截。
代碼(4)定義一個切面,一個切面中可以定義多個攔擊器,其中(4.1)定義了一個前置攔截器,這個攔截器對滿足代碼(3)中定義的切點的連接點(方法)進行攔截,并使用 MyAspect 中定義的通知方法 beforeAdvice 進行功能增強。其中(4.2)定義了一個后置攔截器(finally),對滿足 execution(* *..*BoImpl.sayHello(..))
條件的連接點方法使用 MyAspect 中的通知方法 afterAdvice 進行功能增強。其中(4.3)定義了一個后置攔截器,對滿足execution(* *..*BoImpl.sayHelloAfterReturn(..))
條件的連接點方法使用 MyAspect 中的通知方法 afterReturningAdvice 進行功能增強,這個后置連接器與(4.2)不同在于如果被攔截方法拋出了異常,則這個攔截器不會被執行,而(4.2)的攔截器一直會被執行。其中(4.4)定義了一個當被攔截方法拋出異常后對異常進行攔截的攔截器,具體攔截哪些方法由execution(* *..*BoImpl.sayHelloThrowExecption(..))
來決定,具體的通知方法是 MyAspect 中的 afterThrowingAdvice 方法。其中(4.5)對滿足execution(* *..*BoImpl.sayHelloAround(..))
條件的連接點方法使用 MyAspect 中的通知方法 aroundAdvice 進行增強。
代碼(5)創建一個方法攔截器,它是一個通知,代碼如下:
class TracingInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation i) throws Throwable {
System.out
.println("---method " + i.getMethod + " is called on " + i.getThis + " with args " + i.getArguments+"---");
Object ret = i.proceed;
System.out.println("---method " + i.getMethod + " returns " + ret+"---");
return ret;
}
}
代碼(6)創建了一個新的 aop:config 標簽,內部首先創建了一個切點,然后創建了一個 advisor(一個小型切面),它對應的通知方法是 tracingInterceptor,對應的切點是 pointcutForadVisor。
需要注意的是為了能夠使用 AOP 命名空間下的 aop 標簽,您需要在 XML 引入下面的 spring-aop schema:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- <bean/> definitions here -->
</beans>
把上面配置收集起來,放入到 beanaop.xml 配置文件后,寫下下面代碼,就可以進行測試:
public class TestAOP {
public static final String xmlpath = "beanaop.xml";
public static void main(String[] args) {
ClassPathXmlApplicationContext cpxa = new ClassPathXmlApplicationContext(xmlpath);
HelloServiceBo serviceBo = cpxa.getBean("helloService",HelloServiceBo.class);
serviceBo.sayHello(" I love you");
String result = serviceBo.sayHelloAround("I love you");
System.out.println(result);
result = serviceBo.sayHelloAfterReturn("I love you");
System.out.println(result);
serviceBo.sayHelloAdvisor("I love you");
serviceBo.sayHelloThrowExecption;
}
}
既然本文講解基于 XML 配置的 AOP 的原理,那么我們就先從解析 XML 里面配置的 aop:config 講起。首先看看 Spring 框架的 ConfigBeanDefinitionParser 類是如何解析aop:config 標簽,主要時序圖如下:
enter image description here
代碼(3)注冊了一個 AspectJAwareAdvisorAutoProxyCreator 類到 Spring IOC 容器,該類的作用是自動創建代理類,這個后面會講。這里需要注意的是在 registerAspectJAutoProxyCreatorIfNecessary的useClassProxyingIfNecessary 方法里面解析了 aop:config 標簽里面的 proxy-target-class 和 expose-proxy 屬性值,代碼如下:
private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, @able Element sourceElement) {
//解析proxy-target-class屬性
if (sourceElement != ) {
boolean proxyTargetClass = Boolean.valueOf(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE)); //設置proxy-target-class屬性值到AspectJAwareAdvisorAutoProxyCreator里面
if (proxyTargetClass) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
//解析expose-proxy屬性值
boolean exposeProxy = Boolean.valueOf(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE));
//設置expose-proxy屬性屬性值到AspectJAwareAdvisorAutoProxyCreator
if (exposeProxy) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
針對 proxy-target-class 屬性(默認 false),如果設置為 true,則會強制使用 CGLIB 生成代理類;對于 expose-proxy 屬性(默認 false)如果設置為 true,則對一個類里面的嵌套方法調用的方法也進行代理,具體說是如果一個 ServiceImpl 類里面有 a 和 b 方法,如果 a 里面調用了 b:
public void a{
this.b;
}
那么默認情況下在對方法 a 進行功能增強時候,里面的 b 方法是不會被增強的,如果也需要對 b 方法進行增強則可以設置 expose-proxy 為 true,并且在調用 b 方法時候使用下面方式:
public void a{
((ServiceBo)AopContext.currentProxy).b;
}
需要注意的是 AopContext.currentProxy 返回的是代理后的類,如果使用 JDK 代理則這里類型轉換時候必須要用接口類,如果是 CGLIB 代理則這里類型轉換為實現類 ServiceImpl。
代碼(4)是對 aop:config 標簽里面的 aop:pointcut 元素進行解析,每個 pointcut 元素會創建一個 AspectJExpressionPointcut 的 bean 定義,并注冊到Spring IOC,parsePointcut 的主干代碼如下:
private AbstractBeanDefinition parsePointcut(Element pointcutElement, ParserContext parserContext) {
//獲取aop:pointcut標簽的id屬性值
String id = pointcutElement.getAttribute(ID);
//獲取aop:pointcut標簽的expression屬性值
String expression = pointcutElement.getAttribute(EXPRESSION);
AbstractBeanDefinition pointcutDefinition = ;
try {
//創建AspectJExpressionPointcut的bean定義,并設置屬性
pointcutDefinition = createPointcutDefinition(expression);
pointcutDefinition.setSource(parserContext.extractSource(pointcutElement));
//如果aop:pointcut標簽的id屬性值不為空,則id作為該bean在Spring容器里面的bean名字,并注冊該bean到Spring容器。
String pointcutBeanName = id;
if (StringUtils.hasText(pointcutBeanName)) {
parserContext.getRegistry.registerBeanDefinition(pointcutBeanName, pointcutDefinition);
}
else {//否者系統自動生成一個名字,并注入該bean到Spring容器
pointcutBeanName = parserContext.getReaderContext.registerWithGeneratedName(pointcutDefinition);
}
...
}
...
return pointcutDefinition;
}
注:AspectJExpressionPointcut 的成員變量 expression 保存了 aop:pointcut 標簽的 expression 屬性值,這個在自動代理時候會用到。
代碼(5)是對 aop:config 標簽里面的 aop:advisor 元素進行解析,每個 advisor 元素會創建一個 DefaultBeanFactoryPointcutAdvisor 的 bean 定義,并注冊到 Spring IOC,parseAdvisor代碼如下:
private void parseAdvisor(Element advisorElement, ParserContext parserContext) {
//創建DefaultBeanFactoryPointcutAdvisor的定義,并設置對advice的引用
AbstractBeanDefinition advisorDef = createAdvisorBeanDefinition(advisorElement, parserContext);
String id = advisorElement.getAttribute(ID);
try {
...
//注冊DefaultBeanFactoryPointcutAdvisor到Spring容器
String advisorBeanName = id;
if (StringUtils.hasText(advisorBeanName)) {
parserContext.getRegistry.registerBeanDefinition(advisorBeanName, advisorDef);
}
else {
advisorBeanName = parserContext.getReaderContext.registerWithGeneratedName(advisorDef);
}
//解析aop:advisor標簽中引用的切點,并設置到DefaultBeanFactoryPointcutAdvisor的定義
Object pointcut = parsePointcutProperty(advisorElement, parserContext);
if (pointcut instanceof String) {
advisorDef.getPropertyValues.add(POINTCUT, new RuntimeBeanReference((String) pointcut));
...
}
...
}
...
}
注:每個 DefaultBeanFactoryPointcutAdvisor 對象里面通過成員變量 adviceBeanName 保存引用通知在 BeanFactory 里面的 Bean 名稱,通過成員變量 pointcut 保存切點。
DefaultBeanFactoryPointcutAdvisor 繼承自 Advisor 接口,這里需要注意的是 adviceBeanName 保存的只是通知的字符串名稱,那么如何獲取真正的通知對象呢,其實 DefaultBeanFactoryPointcutAdvisor 實現了 BeanFactoryAware,其內部保證著 BeanFactory 的引用,既然有了 BeanFactory,那么就可以根據 Bean 名稱拿到想要的 Bean,這個可以參考 Chat:Spring 框架常用擴展接口揭秘(https://gitbook.cn/gitchat/activity/5a84589a1f42d45a333f2a8e)。
代碼(6)是對 aop:config 標簽里面的 aop:aspect 元素進行解析,會解析 aop:aspect 元素內的子元素,每個子元素會對應創建一個 AspectJPointcutAdvisor 的 bean 定義,并注冊到 Spring IOC,parseAspect的代碼如下:
private void parseAspect(Element aspectElement, ParserContext parserContext) {
//獲取 aop:aspect 元素的id屬性
String aspectId = aspectElement.getAttribute(ID);
//獲取 aop:aspect 元素的ref屬性
String aspectName = aspectElement.getAttribute(REF);
try {
this.parseState.push(new AspectEntry(aspectId, aspectName));
List<BeanDefinition> beanDefinitions = new ArrayList<>;
List<BeanReference> beanReferences = new ArrayList<>;
...
//循環解析```aop:aspect```元素內所有通知
NodeList nodeList = aspectElement.getChildNodes;
boolean adviceFoundAlready = false;
for (int i = 0; i < nodeList.getLength; i++) {
Node node = nodeList.item(i);
//判斷是否為通知節點,比如前置,后者通知等
if (isAdviceNode(node, parserContext)) {
if (!adviceFoundAlready) {
adviceFoundAlready = true;
...
beanReferences.add(new RuntimeBeanReference(aspectName));
}
//根據通知類型的不同,創建不同的通知對象,最后封裝為AspectJPointcutAdvisor的bean定義,并注冊到Spring容器
AbstractBeanDefinition advisorDefinition = parseAdvice(
aspectName, i, aspectElement, (Element) node, parserContext, beanDefinitions, beanReferences);
beanDefinitions.add(advisorDefinition);
}
}
//循環解析```aop:aspect```元素內所有切點,并注冊到Spring容器
List<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT);
for (Element pointcutElement : pointcuts) {
parsePointcut(pointcutElement, parserContext);
}
...
}
finally {
...
}
}
需要注意的是 parseAdvice 函數內部會根據通知類型的不同創建不同的 advice 對象,對應 before 通知會創建通知對象為 AspectJMethodBeforeAdvice,對應 after 通知創建的通知對象為 AspectJAfterAdvice,對應 after-returning 通知創建的通知對象為 AspectJAfterReturningAdvice,對應 after-throwing 通知創建的通知對象為 AspectJAfterThrowingAdvice,對應 around 通知創建的通知對象為 AspectJAroundAdvice,一個共同點是這些通知對象都實現了 MethodInterceptor 接口。
注:每個 AspectJPointcutAdvisor 對象里面通過 advice 維護著一個通知,通過 pointcut 維護這么一個切點,AspectJPointcutAdvisor 繼承自 Advisor 接口。
至此 Spring 框架把 aop:config 標簽里面的配置全部轉換為了 Bean 定義的形式并注入到 Spring 容器了,需要注意的是對應一個 Spring 應用程序上下文的 XML 配置里面可以配置多個 aop:config 標簽,每次解析一個 aop:config 標簽的時候都會重新走這個流程。
(1)代理類生成的主干流程
上節在解析 aop:config 標簽時候注入了一個 AspectJAwareAdvisorAutoProxyCreator 類到 Spring 容器,其實這個類就是實現動態生成代理類的,AspectJAwareAdvisorAutoProxyCreator 實現了 BeanPostProcessor 接口(這個接口是 Spring 框架在 bean 初始化前后做事情的擴展接口,具體可以參考:http://gitbook.cn/gitchat/activity/5a84589a1f42d45a333f2a8e),所以會有 Object postProcessAfterInitialization(@able Object bean, String beanName) 方法,下面看下這個方法代碼執行時序圖:
enter image description here
代碼(3)在 Spring 容器中查找可以對當前 bean 進行增強的通知 bean,內部首先執行代碼(4)在 Spring 容器查找所有實現了 Advisor 接口的 Bean,也就是上節講解的從 aop:config 標簽里面解析的所有 DefaultBeanFactoryPointcutAdvisor 和 AspectJPointcutAdvisor 的實例都會被找到。代碼(5)則看找到的 Advisor 里面哪些可以應用到當前 bean 上,這個是通過切點表達式來匹配的。代碼(6)則對匹配的 Advisor 進行排序,根據每個 Advisor 對象的 getOrder 方法的值進行排序。
代碼(13)這里根據一些條件決定是使用 JDK 動態代理,還是使用 CGLIB 進行代理,屬于設計模式里面的策略模式。
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
//如果optimize =true或者proxyTargetClass=ture 或者沒有指定代理接口,則使用CGLIB進行代理
if (config.isOptimize || config.isProxyTargetClass || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass;
//如果目標類為接口或者目標類是使用JDK動態代理生成的類,則是要使用JDK對其進行代理
if (targetClass.isInterface || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
//否者使用CGLIB進行代理
return new ObjenesisCglibAopProxy(config);
}
//使用JDK進行代理
else {
return new JdkDynamicAopProxy(config);
}
對于 proxyTargetClass 前面已經講過了可以通過在 aop:config 這個標簽里面配置,那么 optimize 是在哪里配置的呢?其實除了本文講的基于標簽的 AOP 模式,Spring 還提供了比如下面的方式進行動態代理:
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames" value="*impl"></property> <!-- 只為后綴為"impl"的bean生產代理 -->
<property name="interceptorNames" value="myAdvisor"></property> <!--自定義方法攔擊器-->
<property name="optimize" value="true"></property>
</bean>
BeanNameAutoProxyCreator 可以針對指定規則的 beanName 的 bean 使用 interceptorNames 進行增強,由于這時候不能確定匹配的 bean 是否有接口,所以這里 optimize 設置為 true,然后在創建代理工廠時候具體看被代理的類是否是接口決定是使用 JDK 代理還是 CGLIB 代理。
代碼(14)則是具體調用 JdkDynamicAopProxy 或者 ObjenesisCglibAopProxy 的 getProxy 方法獲取代理類,下面兩節具體介紹如何生成。
(2)JDK 動態代理生成代理對象
首先看下 JdkDynamicAopProxy 的 getProxy 方法:
public Object getProxy(@able ClassLoader classLoader) {
if (logger.isDebugEnabled) {
logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource);
}
Class<?> proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
由于 JdkDynamicAopProxy 類實現了 InvocationHandler 接口,所以這里使用 Proxy.newProxyInstance 創建代理對象時候第三個參數傳遞的為 this。下面看下 JdkDynamicAopProxy 的 invoke 方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
...
try {
...
//獲取可以運用到該方法上的攔截器列表
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
//創建一個invocation方法,這個里面是一個interceptor鏈,是一個責任鏈模式
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
//使用攔截器鏈處理這個連接點方法
retVal = invocation.proceed;
...
return retVal;
}
finally {
...
}
}
下面看下上面小節“AOP 簡單使用”例子中調用 serviceBo.sayHello 時候的時序圖從而加深理解:
“AOP 簡單使用”這一小節例子中我們在 sayHello 方法執行前加了一個前置攔截器,在 sayHello 方法執行后加了個后置攔截器;
當執行 serviceBo.sayHello 時候實際上執行的代理類的 sayHello 方法,所以會被 JdkDynamicAopProxy 的 invoke 方法攔截,invoke 方法內首先調用 getInterceptorsAndDynamicInterceptionAdvice 方法獲取配置到 sayHello 方法上的攔截器列表,然后創建一個 ReflectiveMethodInvocation 對象(內部是一個基于數數組的責任鏈),然后調用該對象的 procced 方法激活攔截器鏈對 sayHello 方法進行增強,這里是首先調用了前置連接器對 sayHello 進行增強,然后調用實際業務方法 sayHello,最后調用了后置攔截器對 sayHello 進行增強。
(3)CGLIB 動態代理生成代理對象
首先看下 ObjenesisCglibAopProxy 的 getProxy 方法:
public Object getProxy(@able ClassLoader classLoader) {
try {
Class<?> rootClass = this.advised.getTargetClass;
//創建CGLIB Enhancer
Enhancer enhancer = createEnhancer;
...
enhancer.setSuperclass(proxySuperClass);
enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
...
//獲取callback,主要是DynamicAdvisedInterceptor
Callback callbacks = getCallbacks(rootClass);
Class<?> types = new Class<?>[callbacks.length];
for (int x = 0; x < types.length; x++) {
types[x] = callbacks[x].getClass;
}
//設置callback
enhancer.setCallbackTypes(types);
//產生代理類并創建一個代理實例
return createProxyClassAndInstance(enhancer, callbacks);
}
catch (CodeGenerationException | IllegalArgumentException ex) {
...
}
...
}
下面看下攔截器 DynamicAdvisedInterceptor 的 intercept 方法,代碼如下:
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
...
TargetSource targetSource = this.advised.getTargetSource;
try {
...
//獲取可以運用到該方法上的攔截器列表
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
Object retVal;
//如果攔截器列表為空,則直接反射調用業務方法
if (chain.isEmpty && Modifier.isPublic(method.getModifiers)) {
Object argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = methodProxy.invoke(target, argsToUse);
}
else {
//創建一個方法invocation,其實這個是個攔截器鏈,這里調用proceed激活攔截器鏈對當前方法進行功能增強
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed;
}
//處理返回值
retVal = processReturnType(proxy, target, method, retVal);
return retVal;
}
finally {
...
}
}
下面看“AOP 簡單使用”這一節例子中調用 serviceBo.sayHello 時候的調用鏈路時序圖從而加深理解:
enter image description here
由于默認使用的 JDK 動態代理,要使用 CGLIB 進行代理,需要在 aop:config 標簽添加屬性如下:
<aop:config proxy-target-class="true">
執行流程類似于 JDK 動態代理,這里不再累述。
Spring 框架中如何基于 AOP 實現的事務管理
XML 使用標簽配置事務,一般是按照下面方式進行配置:
<aop:config>
<!--(1) -->
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
<!--(2) -->
<aop:advisor
pointcut-ref="businessService"
advice-ref="tx-advice"/>
</aop:config>
<!--(3) -->
<tx:advice id="tx-advice">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
如上配置(1),配置了一個 id 為 businessService 的切點用來匹配要對哪些方法進行事務增強;
配置(2)配置了一個 advisor ,使用 businessService 作為切點,tx-advice 作為通知方法;
配置(3)則使用 tx:advice 標簽配置了一個通知,這個是重點,下節專門講解。
注:這個配置作用是對滿足 id 為 businessService 的切點條件的方法進行事務增強,并且設置事務傳播性級別為 REQUIRED。
tx:advice 標簽是使用 TxAdviceBeanDefinitionParser 進行解析的,下面看看解析時序圖:
enter image description here
如上時序圖 BeanDefinitionBuilder 是一個建造者模式,用來構造一個 bean 定義,這個 bean 定義最終會生成一個 TransactionInterceptor 的實例;
步驟(5)通過循環解析 tx:attributes 標簽里面的所有 tx:method 標簽,每個 tx:method 對應一個 RuleBasedTransactionAttribute 對象,其中 tx:method 標簽中除了可以配置事務傳播性,還可以配置事務隔離級別,超時時間,是否只讀,和回滾策略。
步驟(12)把所有的 method 標簽的 RuleBasedTransactionAttribute 對象存在到了一個 map 數據結構,步驟(13)設置解析的所有屬性到建造者模式的對象里面,步驟(14)使用建造者對象創建一個 bean 定義,步驟(15)則注冊該 bean 到 Spring 容器。
注:tx:advice 作用是創建一個 TransactionInterceptor 攔截器,內部維護事務配置信息。
下面看下 TransactionInterceptor 的 invoke 方法:
public Object invoke(final MethodInvocation invocation) throws Throwable {
...
return invokeWithinTransaction(invocation.getMethod, targetClass, invocation::proceed);
}
protected Object invokeWithinTransaction(Method method, @able Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
// If the transaction attribute is , the method is non-transactional.
TransactionAttributeSource tas = getTransactionAttributeSource;
final TransactionAttribute txAttr = (tas != ? tas.getTransactionAttribute(method, targetClass) : );
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
if (txAttr == || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// 標準事務,內部有getTransaction(開啟事務) 和commit(提交)/rollback(回滾)事務被調用.
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = ;
try {
//這是一個環繞通知,調用proceedWithInvocation激活攔截器鏈里面的下一個攔擊器
retVal = invocation.proceedWithInvocation;
}
catch (Throwable ex) {
// target invocation exception
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
commitTransactionAfterReturning(txInfo);
return retVal;
}
...
}
其中 createTransactionIfNecessary 是重要方法,其內部邏輯處理流程如下圖:
enter image description here
注:Spring 事務管理通過配置一個 AOP 切面來實現,其中定義了一個切點用來決定對哪些方法進行方法攔截,定義了一個 TransactionInterceptor 通知,來對攔截到的方法進行事務增強,具體事務攔擊器里面是怎么做的,讀者可以結合上面的 TransactionInterceptor 方法的流程圖結合源碼來研究下,如果必要后面會再開一個 Chat 專門講解 Spring 事務的實現,以及事務隔離性與傳播性。
總結
本文以大綱的形式剖析了 AOP 原理,以及事務攔擊器如何使用 AOP 來實現,由于篇幅限制并沒有深入到每個實現細節進行講解,希望讀者能夠依靠本文作為大綱,對照源碼深入到大綱里面的細節進行研究,以便加深對 AOP 原理的理解和掌握。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。