文章目录
-
- 例子准备
- 一,执行流程
- 二,获取拦截器链
- 三,执行拦截器链
-
- 1,ExposeInvocationInterceptor
- 2,AspectJAfterThrowingAdvice
- 3,AfterReturningAdviceInterceptor
- 4,AspectJAfterAdvice
- 5,AspectJAroundAdvice
- 6,MethodBeforeAdviceInterceptor
- 7,返回到AspectJAroundAdvice
- 8,返回到AspectJAfterAdvice
- 9,返回到AfterReturningAdviceInterceptor
- 10,返回到AspectJAfterThrowingAdvice
- 11,返回到ExposeInvocationInterceptor
- 四,总结
例子准备
LogUtil.java
public class LogUtil {
private int start(JoinPoint joinPoint){
//获取方法签名
Signature signature = joinPoint.getSignature();
//获取参数信息
Object[] args = joinPoint.getArgs();
System.out.println("log---"+signature.getName()+"方法开始执行:参数是"+Arrays.asList(args));
return 100;
}
public static void stop(JoinPoint joinPoint,Object result){
Signature signature = joinPoint.getSignature();
System.out.println("log---"+signature.getName()+"方法执行结束,结果是:"+result);
}
public static void logException(JoinPoint joinPoint,Exception e){
Signature signature = joinPoint.getSignature();
System.out.println("log---"+signature.getName()+"方法抛出异常:"+e.getMessage());
}
public static void logFinally(JoinPoint joinPoint){
Signature signature = joinPoint.getSignature();
System.out.println("log---"+signature.getName()+"方法执行结束。。。。。over");
}
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Signature signature = pjp.getSignature();
Object[] args = pjp.getArgs();
Object result = null;
try {
System.out.println("log---环绕通知start:"+signature.getName()+"方法开始执行,参数为:"+Arrays.asList(args));
result = pjp.proceed(args);
System.out.println("log---环绕通知stop"+signature.getName()+"方法执行结束");
} catch (Throwable throwable) {
System.out.println("log---环绕异常通知:"+signature.getName()+"出现异常");
throw throwable;
}finally {
System.out.println("log---环绕返回通知:"+signature.getName()+"方法返回结果是:"+result);
}
return result;
}
}
MyCalculator.java
public class MyCalculator{
public Integer add(Integer i, Integer j){
Integer result = i+j;
return result;
}
public Integer sub(Integer i, Integer j){
Integer result = i-j;
return result;
}
public Integer mul(Integer i, Integer j){
Integer result = i*j;
return result;
}
public Integer div(Integer i, Integer j){
Integer result = i/j;
return result;
}
}
boboAopXmlTest.xml
<?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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
">
<bean id="logUtil" class="com.bobo.aop.xml.LogUtil"></bean>
<bean id="myCalculator" class="com.bobo.aop.xml.MyCalculator" ></bean>
<aop:config>
<aop:aspect ref="logUtil">
<aop:pointcut id="myPoint" expression="execution( Integer com.bobo.aop.xml.MyCalculator.* (..))"/>
<aop:around method="around" pointcut-ref="myPoint"></aop:around>
<aop:before method="start" pointcut-ref="myPoint"></aop:before>
<aop:after method="logFinally" pointcut-ref="myPoint"></aop:after>
<aop:after-returning method="stop" pointcut-ref="myPoint" returning="result"></aop:after-returning>
<aop:after-throwing method="logException" pointcut-ref="myPoint" throwing="e"></aop:after-throwing>
</aop:aspect>
</aop:config>
</beans>
测试类
public class MyTest {
public static void main(String[] args) {
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/home/bobo");
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("boboAopXmlTest.xml");
MyCalculator bean = context.getBean(MyCalculator.class);
bean.add(1, 1);
}
}
一,执行流程
我们还是用之前xml配置aop方式的例子演示:
public class MyTest {
public static void main(String[] args) {
// 输出生成的代理类到/home/bobo 目录
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/home/bobo");
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("boboAopXmlTest.xml");
MyCalculator bean = context.getBean(MyCalculator.class);
bean.add(1, 1);
}
}
我们调试可以看到,获取到的bean是cglib生成的动态代理对象:
下面我们先来看一下代理类MyCalculator$$EnhancerBySpringCGLIB$$cfa21dd
(我们删除了大量代码,只保留部分与add()方法相关的):
public class MyCalculator$$EnhancerBySpringCGLIB$$cfa21dd extends MyCalculator implements SpringProxy, Advised, Factory {
private static final Method CGLIB$add$3$Method;
private static final MethodProxy CGLIB$add$3$Proxy;
static void CGLIB$STATICHOOK1() {
CGLIB$emptyArgs = new Object[0];
Class var0 = Class.forName("com.bobo.aop.xml.MyCalculator$$EnhancerBySpringCGLIB$$cfa21dd");
var10000 = ReflectUtils.findMethods(new String[]{
"sub", "(Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Integer;", "mul", "(Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Integer;", "div", "(Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Integer;", "add", "(Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Integer;"}, (var1 = Class.forName("com.bobo.aop.xml.MyCalculator")).getDeclaredMethods());
CGLIB$add$3$Method = var10000[3];
CGLIB$add$3$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Integer;", "add", "CGLIB$add$3");
}
final Integer CGLIB$add$3(Integer var1, Integer var2) {
return super.add(var1, var2);
}
public final Integer add(Integer var1, Integer var2) {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
return var10000 != null ? (Integer)var10000.intercept(this, CGLIB$add$3$Method, new Object[]{
var1, var2}, CGLIB$add$3$Proxy) : super.add(var1, var2);
}
public static MethodProxy CGLIB$findMethodProxy(Signature var0) {
String var10000 = var0.toString();
switch(var10000.hashCode()) {
case 720618747:
if (var10000.equals("add(Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Integer;")) {
return CGLIB$add$3$Proxy;
}
break;
}
return null;
}
public void setCallbacks(Callback[] var1) {
this.CGLIB$CALLBACK_0 = (MethodInterceptor)var1[0];
this.CGLIB$CALLBACK_1 = (MethodInterceptor)var1[1];
this.CGLIB$CALLBACK_2 = (NoOp)var1[2];
this.CGLIB$CALLBACK_3 = (Dispatcher)var1[3];
this.CGLIB$CALLBACK_4 = (Dispatcher)var1[4];
this.CGLIB$CALLBACK_5 = (MethodInterceptor)var1[5];
this.CGLIB$CALLBACK_6 = (MethodInterceptor)var1[6];
}
static {
CGLIB$STATICHOOK1();
}
}
我们来看上面代理类的add()
方法,会调用到CGLIB$CALLBACK_0
的intercept()
方法。
由上图可知道,CGLIB$CALLBACK_0
是CglibAopProxy$DynamicAdvisedInterceptor
,所以回调到CglibAopProxy$DynamicAdvisedInterceptor
类的intercept()
方法中:
/**
* AopProxy的子类。使用Cglib的方法创建代理对象。它持有Advised对象。
*/
@SuppressWarnings("serial")
class CglibAopProxy implements AopProxy, Serializable {
private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {
private final AdvisedSupport advised;
public DynamicAdvisedInterceptor(AdvisedSupport advised) {
this.advised = advised;
}
@Override
@Nullable
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
Object target = null;
TargetSource targetSource = this.advised.getTargetSource();
try {
if (this.advised.exposeProxy) {
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null);
// 获取拦截器通知链
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
Object retVal;
// 如果没有aop通知配置,那么直接调用target对象的调用方法
if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
// 如果拦截器链为空则直接激活原方法
retVal = methodProxy.invoke(target, argsToUse);
}
else {
// 通过cglibMethodInvocation来启动advice通知
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
}
retVal = processReturnType(proxy, target, method, retVal);
return retVal;
}
}
}
}
intercept():方法参数:
- proxy:代理类对象:
- method:实际类方法,
- args:参数
- methodProxy:代理类方法
intercept()方法主要完成如下步骤:
1、 获取适合当前方法的拦截器链;
2、 如果拦截器链为空,则直接通过反射执行目标方法;
3、 若拦截器链不为空,则创建方法调用ReflectiveMethodInvocation对象的proceed()方法启动拦截器链;
二,获取拦截器链
public class DefaultAdvisorChainFactory implements AdvisorChainFactory, Serializable {
@Override
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
Advised config, Method method, @Nullable Class<?> targetClass) {
// AdvisorAdapterRegistry这个类的主要作用是将Advice适配为Advisor 将Advisor适配为对应的MethodInterceptor
AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
Advisor[] advisors = config.getAdvisors();
// 创建一个初始大小为 之前获取到的 通知个数的集合
List<Object> interceptorList = new ArrayList<>(advisors.length);
// 如果目标类为null的话,则从方法签名中获取目标类
Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());
// 循环目标方法匹配的通知
for (Advisor advisor : advisors) {
// 如果是PointcutAdvisor类型的实例
if (advisor instanceof PointcutAdvisor) {
// Add it conditionally.
PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
// 如果提前进行过切点的匹配了或者当前的Advisor适用于目标类
if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
boolean match;
//检测Advisor是否适用于此目标方法
if (mm instanceof IntroductionAwareMethodMatcher) {
if (hasIntroductions == null) {
hasIntroductions = hasMatchingIntroductions(advisors, actualClass);
}
match = ((IntroductionAwareMethodMatcher) mm).matches(method, actualClass, hasIntroductions);
}
else {
match = mm.matches(method, actualClass);
}
if (match) {
// 拦截器链是通过AdvisorAdapterRegistry来加入的,这个AdvisorAdapterRegistry对advice织入具备很大的作用
MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
// 使用MethodMatchers的matches方法进行匹配判断
if (mm.isRuntime()) {
// 动态切入点则会创建一个InterceptorAndDynamicMethodMatcher对象
// 这个对象包含MethodInterceptor和MethodMatcher的实例
for (MethodInterceptor interceptor : interceptors) {
interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));
}
}
else {
// 添加到列表中
interceptorList.addAll(Arrays.asList(interceptors));
}
}
}
}
}
return interceptorList;
}
这里先要拿到之前我们创建好的advisors
,在创建的时候已经执行sortAdvisors()
方法进行排序,这里拿过来的是已经排好序列的列表:
下面会去循环遍历每个advisor,判断advisor
是否和当前方法add(1,1)
匹配,先匹配类,再匹配方法,如果都能匹配就把当前Advisor
转化为MethodInterceptor
,加入到interceptorList
集合里面并返回。
public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Serializable {
/**
* 此方法把已有的advice实现的adapter加入进来
*
* Create a new DefaultAdvisorAdapterRegistry, registering well-known adapters.
*/
public DefaultAdvisorAdapterRegistry() {
registerAdvisorAdapter(new MethodBeforeAdviceAdapter());
registerAdvisorAdapter(new AfterReturningAdviceAdapter());
registerAdvisorAdapter(new ThrowsAdviceAdapter());
}
// 将 Advisor转换为 MethodInterceptor
@Override
public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
List<MethodInterceptor> interceptors = new ArrayList<>(3);
// 从Advisor中获取 Advice
Advice advice = advisor.getAdvice();
if (advice instanceof MethodInterceptor) {
interceptors.add((MethodInterceptor) advice);
}
for (AdvisorAdapter adapter : this.adapters) {
if (adapter.supportsAdvice(advice)) {
// 转换为对应的 MethodInterceptor类型
// AfterReturningAdviceInterceptor MethodBeforeAdviceInterceptor ThrowsAdviceInterceptor
interceptors.add(adapter.getInterceptor(advisor));
}
}
if (interceptors.isEmpty()) {
throw new UnknownAdviceTypeException(advisor.getAdvice());
}
return interceptors.toArray(new MethodInterceptor[0]);
}
}
以上源码是把Advisor
转化为MethodInterceptor
过程:
- 其中AspectJAfterAdvice,AspectJAfterThrowingAdvice和AspectJAroundAdvice都实现了MethodInterceptor接口,可以直接强转。
- 而AspectJMethodBeforeAdvice和AspecrtJAfterReturningAdvice实现了AdvisorAdapter接口,需要从Adapter中获取。
我们可以看一下MethodBeforeAdviceAdapter
源码:
class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {
@Override
public boolean supportsAdvice(Advice advice) {
return (advice instanceof MethodBeforeAdvice);
}
@Override
public MethodInterceptor getInterceptor(Advisor advisor) {
MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
return new MethodBeforeAdviceInterceptor(advice);
}
}
其实从Adapter中也是new了一个MethodInterceptor
返回,没有啥区别,为什么要用Adapter呢?其实就是为了方便扩展而已。
最后返回匹配到的拦截器链:
三,执行拦截器链
public class ReflectiveMethodInvocation implements ProxyMethodInvocation, Cloneable {
/**
* 递归获取通知,然后执行
*/
@Override
@Nullable
public Object proceed() throws Throwable {
// 从索引为-1的拦截器开始调用,并按序递增,如果拦截器链中的拦截器迭代调用完毕,开始调用target的函数,这个函数是通过反射机制完成的
// 具体实现在AopUtils.invokeJoinpointUsingReflection方法中
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}
// 获取下一个要执行的拦截器,沿着定义好的interceptorOrInterceptionAdvice链进行处理
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
// 这里对拦截器进行动态匹配的判断,这里是对pointcut触发进行匹配的地方,如果和定义的pointcut匹配,那么这个advice将会得到执行
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
}
else {
// 如果不匹配,那么proceed会被递归调用,知道所有的拦截器都被运行过位置
return proceed();
}
}
else {
// 普通拦截器,直接调用拦截器,将this作为参数传递以保证当前实例中调用链的执行
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}
}
拦截器链的执行过程是按照下标从0--N
一个个的拦截器获取并执行的:
1,ExposeInvocationInterceptor
首先执行ExposeInvocationInterceptor
,也就是拦截器链中下标为 0 的拦截器,执行其invoke()
方法:
public final class ExposeInvocationInterceptor implements MethodInterceptor, PriorityOrdered, Serializable {
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
MethodInvocation oldInvocation = invocation.get();
invocation.set(mi);
try {
// 返回
return mi.proceed();
}
finally {
invocation.set(oldInvocation);
}
}
}
发现并没有执行什么,直接返回。
此时开始处理拦截器链中下标为 1 的拦截器,也就是AspectJAfterThrowingAdvice
2,AspectJAfterThrowingAdvice
public class AspectJAfterThrowingAdvice extends AbstractAspectJAdvice
implements MethodInterceptor, AfterAdvice, Serializable {
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
try {
// 返回
return mi.proceed();
}
catch (Throwable ex) {
// 抛出异常
if (shouldInvokeOnThrowing(ex)) {
// 执行异常通知
invokeAdviceMethod(getJoinPointMatch(), null, ex);
}
throw ex;
}
}
}
执行第一行代码又返回回去了。
此时开始处理拦截器链中下标为 2 的拦截器,也就是AfterReturningAdviceInterceptor
3,AfterReturningAdviceInterceptor
public class AfterReturningAdviceInterceptor implements MethodInterceptor, AfterAdvice, Serializable {
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
// 返回
Object retVal = mi.proceed();
// 返回通知方法
this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
return retVal;
}
}
执行第一行代码又返回回去了。
此时开始处理拦截器链中下标为 3 的拦截器,也就是AspectJAfterAdvice
4,AspectJAfterAdvice
public class AspectJAfterAdvice extends AbstractAspectJAdvice
implements MethodInterceptor, AfterAdvice, Serializable {
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
try {
// 执行下一个通知/拦截器
return mi.proceed();
}
finally {
// 后置通知的方法总是会被执行,原因就在这finally
invokeAdviceMethod(getJoinPointMatch(), null, null);
}
}
}
执行第一行代码又返回回去了。
此时开始处理拦截器链中下标为 4 的拦截器,也就是AspectJAroundAdvice
5,AspectJAroundAdvice
public class AspectJAroundAdvice extends AbstractAspectJAdvice implements MethodInterceptor, Serializable {
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;
ProceedingJoinPoint pjp = lazyGetProceedingJoinPoint(pmi);
JoinPointMatch jpm = getJoinPointMatch(pmi);
return invokeAdviceMethod(pjp, jpm, null, null);
}
}
下面调用around方法:
protected Object invokeAdviceMethodWithGivenArgs(Object[] args) throws Throwable {
Object[] actualArgs = args;
try {
ReflectionUtils.makeAccessible(this.aspectJAdviceMethod);
// 反射调用通知方法
return this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs);
}
}
最后调用到Logutil.java的around()方法中:
public class LogUtil {
// 省略部分代码...
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Signature signature = pjp.getSignature();
Object[] args = pjp.getArgs();
Object result = null;
try {
System.out.println("log---环绕通知start:"+signature.getName()+"方法开始执行,参数为:"+Arrays.asList(args));
result = pjp.proceed(args);
System.out.println("log---环绕通知stop"+signature.getName()+"方法执行结束");
} catch (Throwable throwable) {
System.out.println("log---环绕异常通知:"+signature.getName()+"出现异常");
throw throwable;
}finally {
System.out.println("log---环绕返回通知:"+signature.getName()+"方法返回结果是:"+result);
}
return result;
}
}
我们看到,执行了第一个打引之后,又调用了pjp.proceed(args)
方法进行返回,此时我们看到控制台只有第一行打印输出:
调用了pjp.proceed(args)
方法进行返回
此时开始处理拦截器链中下标为 5 的拦截器,也就是最后一个拦截器了MethodBeforeAdviceInterceptor
6,MethodBeforeAdviceInterceptor
public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice, Serializable {
public Object invoke(MethodInvocation mi) throws Throwable {
// 执行前置通知的方法
this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
// 执行下一个通知/拦截器,但是该拦截器是最后一个了,所以会调用目标方法
return mi.proceed();
}
}
执行before()
方法,会调用到Logutil
的before()方法:
public class LogUtil {
private int start(JoinPoint joinPoint){
//获取方法签名
Signature signature = joinPoint.getSignature();
//获取参数信息
Object[] args = joinPoint.getArgs();
System.out.println("log---"+signature.getName()+"方法开始执行:参数是"+Arrays.asList(args));
return 100;
}
// 省略部分代码...
}
执行完这个方法之后,控制台会把当前打引出来:
接下来会执行mi.proceed()
方法继续返回:由于已经执行到了最后一个方法了,所以会调用目标方法:
public class MyCalculator{
public Integer add(Integer i, Integer j){
Integer result = i+j;
return result;
}
}
执行完目标方法之后会沿着上面执行流程一个个的返回回去。
7,返回到AspectJAroundAdvice
继续执行LogUtil的around()方法的后面部分:
执行完之后,控制台会打印出:
继续返回
8,返回到AspectJAfterAdvice
执行后面finally中的方法:
会执行到LogUtil
中的@After
标注的方法:
public class LogUtil {
public static void logFinally(JoinPoint joinPoint){
Signature signature = joinPoint.getSignature();
System.out.println("log---"+signature.getName()+"方法执行结束。。。。。over");
}
}
执行完之后控制台会输出当前打印:
继续返回
9,返回到AfterReturningAdviceInterceptor
执行后面的方法:
会执行到LogUtil中的被@AfterReturning
标注的方法:
public class LogUtil {
public static void stop(JoinPoint joinPoint,Object result){
Signature signature = joinPoint.getSignature();
System.out.println("log---"+signature.getName()+"方法执行结束,结果是:"+result);
}
}
执行完之后,控制台会输出:
继续返回
10,返回到AspectJAfterThrowingAdvice
public class AspectJAfterThrowingAdvice extends AbstractAspectJAdvice
implements MethodInterceptor, AfterAdvice, Serializable {
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
try {
// 执行下一个通知/拦截器 methodInvocation
return mi.proceed();
}
catch (Throwable ex) {
// 抛出异常
if (shouldInvokeOnThrowing(ex)) {
// 执行异常通知
invokeAdviceMethod(getJoinPointMatch(), null, ex);
}
throw ex;
}
}
}
没有任何异常,所以不输出。
继续返回
11,返回到ExposeInvocationInterceptor
设置一个空值,然后继续返回
然后执行完毕,以上就是拦截器链的执行过程。
四,总结
用一张图总结拦截器链的执行过程:
版权声明:本文不是「本站」原创文章,版权归原作者所有 | 原文地址: