整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

面试官带你学安卓-Handler这些必备知识点你都知道吗?


Android面试中,关于 Handler 的问题是必备的,但是这些关于 Handler 的知识点你都知道吗?

一、题目层次

  1. Handler 的基本原理
  2. 子线程中怎么使用 Handler
  3. MessageQueue 获取消息是怎么等待
  4. 为什么不用 wait 而用 epoll 呢?
  5. 线程和 Handler Looper MessageQueue 的关系
  6. 多个线程给 MessageQueue 发消息,如何保证线程安全
  7. Handler 消息延迟是怎么处理的
  8. View.post 和 Handler.post 的区别
  9. Handler 导致的内存泄漏
  10. 非 UI 线程真的不能操作 View 吗

二、题目详解

代码分析基于 Android SDK 28

大家可以先看上面的问题思考一下,如果都清楚的话,下面的文章也没必要看了~

1. Handler 的基本原理

关于 Handler 的原理,相比不用多说了,大家都应该知道,一张图就可以说明(图片来自网络)。

2. 子线程中怎么使用 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 相关的知识,这里就不细说了。

3. MessageQueue 如何等待消息

上面说到 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 机制呢?

4. 为什么不用 wait 而用 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 的区别,这里也不细说了,大家可以在上面的参考文章中一起看看。

5. 线程和 Handler Looper MessageQueue 的关系

这里的关系是一个线程对应一个 Looper 对应一个 MessageQueue 对应多个 Handler。

6. 多个线程给 MessageQueue 发消息,如何保证线程安全

既然一个线程对应一个 MessageQueue,那多个线程给 MessageQueue 发消息时是如何保证线程安全的呢?
说来简单,就是加了个锁而已。

// MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
    synchronized (this) {
        // ...
    }
}

7. Handler 消息延迟是怎么处理的

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 时候,会执行下面几个步骤:

  1. 将我们传入的延迟时间转化成距离开机时间的毫秒数
  2. MessageQueue 中根据上一步转化的时间进行顺序排序
  3. 在 MessageQueue.next 获取消息时,对比当前时间(now)和第一步转化的时间(when),如果 now < when,则通过 epoll_wait 的 timeout 进行等待
  4. 如果该消息需要等待,会进行 idel handlers 的执行,执行完以后会再去检查此消息是否可以执行

8. View.post 和 Handler.post 的区别

我们最常用的 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 的区别就是:

  1. 如果在 performTraversals 前调用 View.post,则会将消息进行保存,之后在 dispatchAttachedToWindow 的时候通过 ViewRootImpl 中的 Handler 进行调用。
  2. 如果在 performTraversals 以后调用 View.post,则直接通过 ViewRootImpl 中的 Handler 进行调用。

这里我们又可以回答一个问题了,就是为什么 View.post 里可以拿到 View 的宽高信息呢?
因为 View.post 的 Runnable 执行的时候,已经执行过 performTraversals 了,也就是 View 的 measure layout draw 方法都执行过了,自然可以获取到 View 的宽高信息了。

9. Handler 导致的内存泄漏

这个问题就是老生常谈了,可以由此再引申出内存泄漏的知识点,比如:如何排查内存泄漏,如何避免内存泄漏等等。

10. 非 UI 线程真的不能操作 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

  1. Looper.prepare 创建 Looper 并添加到 ThreadLocal 中
  2. Looper.loop 启动 Looper 的循环

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 消息延迟是怎么处理的

  1. 将传入的延迟时间转化成距离开机时间的毫秒数
  2. MessageQueue 中根据上一步转化的时间进行顺序排序
  3. 在 MessageQueue.next 获取消息时,对比当前时间(now)和第一步转化的时间(when),如果 now < when,则通过 epoll_wait 的 timeout 进行等待
  4. 如果该消息需要等待,会进行 idel handlers 的执行,执行完以后会再去检查此消息是否可以执行

8. View.post 和 Handler.post 的区别

View.post 最终也是通过 Handler.post 来执行消息的,执行过程如下:

  1. 如果在 performTraversals 前调用 View.post,则会将消息进行保存,之后在 dispatchAttachedToWindow 的时候通过 ViewRootImpl 中的 Handler 进行调用。
  2. 如果在 performTraversals 以后调用 View.post,则直接通过 ViewRootImpl 中的 Handler 进行调用。

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 的源码分析

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的源码分析

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 动态代理

由于 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 类;目标对象与代理对象没有什么直接关系,只是它们都实现了接口,并且代理对象执行方法时候内部最终是委托目标对象执行具体的方法。

CGLIB 动态代理

相比 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 的实现原理。

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;

}

}

原理剖析

aop:config 标签的解析

既然本文讲解基于 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 标签的解析

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 原理的理解和掌握。