在Spring Aop的源码中,关于拦截器的调度顺序是一个值得分析的点,也可以让我们深入的理解关于@After,@Before,@Around,@AfterReturning的相关执行顺序,废话不多说,直接上源码
无论是基于Xml还是基于注解的方式,实际上都是在Spring的ioc容器中注入一个BeanPostProcessor类型的AnnotationAwareAspectJAutoProxyCreator类,并且该类有两个比较重要的属性,proxyTargetClass和exposeProxy。
补充:
对于上面的exposeProxy中的 含有增强方法的被代理类的a方法内部通过this.b()调用通用含有增强方法的b的时候,b实际上不增强的问题我们可以很简单的举一个例子说明一哈:
例如,我们创建一个接口:HelloTest
public interface HelloTest {
int say(String name);
void hello();
}
实现类:HelloTestImpl
public class HelloTestImpl implements HelloTest{
@Override
public int say(String name) {
System.out.println("Hello:" + name);
this.hello();
return 0;
}
@Override
public void hello() {
System.out.println("你好,zhoucg");
}
}
InvocationHandler类:CustomInvocationHandler
public class CustomInvocationHandler implements InvocationHandler {
private Object target ;
public CustomInvocationHandler(Object target) {
this.target = target;
}
/**
* 设置被动态代理的类的增强方法
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before invocation==============");
Object retVal = method.invoke(target,args);
System.out.println("After invocation===============");
return retVal;
}
}
我们通过JDK的动态代理,看一下
//设置为true,会在工程根目录生成$Proxy0.class代理类(com.sun.proxy.$Proxy0.class)
System.getProperties().put(
"sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//String saveGeneratedFiles = System.getProperty("sun.misc.ProxyGenerator.saveGeneratedFiles");
//System.out.println(saveGeneratedFiles);
HelloTest helloWord = new HelloTestImpl();
CustomInvocationHandler customInvocationHandler = new CustomInvocationHandler(
helloWord);
//通过Proxy.newProxyInstance生成代理对象
HelloTest proxy = (HelloTest) Proxy.newProxyInstance(
HelloTest.class.getClassLoader(),
helloWord.getClass().getInterfaces(), customInvocationHandler);
//调用say方法
proxy.say("test");
这个时候,我们反编译获取到的动态代理类:$Proxy0
public final class $Proxy0 extends Proxy implements HelloTest {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m4;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final void hello() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int say(String var1) throws {
try {
return ((Integer)super.h.invoke(this, m4, new Object[]{var1})).intValue();
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
。。。。。 equals,hash,tostring。。。
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
m3 = Class.forName("com.zcswl.pattern.proxy.HelloTest").getMethod("hello", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m4 = Class.forName("com.zcswl.pattern.proxy.HelloTest").getMethod("say", new Class[]{Class.forName("java.lang.String")});
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
我们会很清晰的看到:
当执行 proxy.say(“test”);的时候,实际上是执行了
public final int say(String var1) throws {
try {
return ((Integer)super.h.invoke(this, m4, new Object[]{var1})).intValue();
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
方法,内部调用了InvocationHandler的invoke方法进行执行,实际上就是执行了CustomInvocationHandler中的invoke方法
这里的
return ((Integer)super.h.invoke(this, m4, new Object[]{var1})).intValue(); 是proxy的代理对象,我们在看对应的CustomInvocationHandler中的invoke的方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before invocation==============");
Object retVal = method.invoke(target,args);
System.out.println("After invocation===============");
return retVal;
}
因为其method的invoke的参数是target,即目标对象(注意这里不是代理对象),因此其自然是没有走对应的代理对象的,因此不会执行其对应的增强方法
这里,肯定有人会问,那既然CustomInvocationHandler中的invoke方法中的 Object retVal = method.invoke(target,args); 传递的是targer,即目标对象,那我这样写
Object retVal = method.invoke(proxy,args); 不就好了
这个想法很危险,你这个就违背了动态代理的设计初衷,你这样写,实际上就没得代理了,动态代理就是代理对应的目标对象,并进行增强。
这样写直接是会导致栈溢出,一直调用错误
可以参考我的另一个文章:Proxy
正题:
首先,我们定义一个接口ITest
public interface ITest {
void test();
void subTest();
String afterReturn(String a);
}
定义接口的实现类(被代理对象)TestBean
public class TestBean implements ITest{
private String testStr = "testStr";
public String getTestStr() {
return testStr;
}
public void setTestStr(String testStr) {
this.testStr = testStr;
}
@Override
public void test() {
System.out.println("内部执行====test");
this.subTest();
System.out.println(AopContext.currentProxy() == this);
// boolean aopProxy = AopUtils.isAopProxy(this);
// boolean aopProxy1 = AopUtils.isAopProxy(AopContext.currentProxy());
// System.out.println(this);
//(ITest)AopContext.currentProxy().subTest();
}
@Override
public void subTest() {
System.out.println("内部执行=======subTest");
}
@Override
public String afterReturn(String a) {
return a+"zcg";
}
@Override
public String toString() {
return "TestBean{" +
"testStr='" + testStr + '\'' +
'}';
}
}
定义切面,和相关的切入点,以及对应的增强方法AspectJTest
@Aspect
public class AspectJTest {
@Pointcut("execution(* com.zcs.aop.usedemo.TestBean.test(..))")
public void test() {
}
@Pointcut("execution(* com.zcs.aop.usedemo.TestBean.subTest(..))")
public void subTest() {
}
@Before("test()")
public void beforeTest() {
System.out.println("beforeTest");
}
@After("test()")
public void afterTest() {
System.out.println("afterTest");
}
@Around("test() || subTest()")
public Object aroundTest(ProceedingJoinPoint proceedingJoinPoint) {
System.out.println("=========beforeSubTest==========");
Object o = null;
try {
o = proceedingJoinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("=========afterSubTest==========");
return o;
}
/**
* AfterReturning 增强处理可以访问到方法的返回值,但它不能改变目标方法的返回值
* @param rvt
* @return
*/
@AfterReturning(returning="rvt", pointcut="execution(* com.zcs.aop.usedemo.TestBean.afterReturn(..))")
public String afterRet(String rvt) {
return rvt+" wl";
}
}
这里,我们通过XML配置的方式,
执行代码:
@Test
public void testAopDemo() {
ApplicationContext context = new ClassPathXmlApplicationContext("aopDemo-Test.xml");
ITest testBean = (ITest) context.getBean("test");
testBean.test();
}
1,在注入了AnnotationAwareAspectJAutoProxyCreator关键的AOP处理类之后,实际上,我们会在Bean的实例化之前执行InstantiationAwareBeanPostProcessor对应的postProcessBeforeInstantiation(Class> beanClass, String beanName)方法,该方法定义在AbstractAutoProxyCreator抽象类
该方法,
2,实际的实例化动态代理对象实际上是在BeaPostProcessor接口的Object postProcessAfterInitialization(@Nullable Object bean, String beanName) 方法中
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
// 根据给定的bean的class和name构建出个key
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (!this.earlyProxyReferences.contains(cacheKey)) {
// 如果它适合被代理,则需要封装指定bean
Object wrapObject = wrapIfNecessary(bean, beanName, cacheKey);
System.out.println(wrapObject);
return wrapObject;
}
}
return bean;
}
在wrapIfNecessary方法中,实际上是首先确认当前容器内的增强器是否适用于当前创建的对象,并获取到适用于创建当前类的增强器
/**
* 1,判断当前容器是否存在对应的切面类(使用@Aspect注释的类)
* 2,遍历每一个切面类的方法,判断是否为对应的增强方法,如果未对应的增强方法,每一个增强方法创建对应的增强类
* 3,判断容器中的增强类适用于当前bean的增强类AbstractAutoProxyCreator#findAdvisorsThatCanApply
*/
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
同时,我们会将对应创建动态代理类的能力交给ProxyFactory进行处理
protected Object createProxy(Class> beanClass, @Nullable String beanName,
@Nullable Object[] specificInterceptors, TargetSource targetSource) {
if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
}
ProxyFactory proxyFactory = new ProxyFactory();
// 获取当前类中的相关属性
proxyFactory.copyFrom(this);
// 决定对于给定的原始bean是否直接对它的类型(targetClass)进行代理而不是对它实现的接口进行代理
// 检查proxyFactory的proxyTargetClass属性设置以及目标bean的beanDefinition中preserveTargetClass属性设置
if (!proxyFactory.isProxyTargetClass()) {
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {
//添加代理接口
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
// 加入增强器
proxyFactory.addAdvisors(advisors);
// 定制代理
proxyFactory.setTargetSource(targetSource);
// 定制代理,留作子类扩展
customizeProxyFactory(proxyFactory);
// 用来控制代理工厂被配置后,是否还允许修改通知
// 缺省值为false(即在代理被配置之后,不允许修改代理的配置)
proxyFactory.setFrozen(this.freezeProxy);
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}
// 委托ProxyFactory去真正创建代理对象
return proxyFactory.getProxy(getProxyClassLoader());
}
并且,在创建动态代理,我们会通过proxyTargetClass参数分析是使用CGLIB动态代理还是JDK动态代理,在这个示例中,系统会使用JdkDynamicAopProxy创建动态代理对象,
@Override
public Object getProxy(@Nullable 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);
// 该类实现了InvocationHandler接口,所以传this
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
一系列山穷水绕的逻辑之后,我们会看到我们熟悉的通过JDK创建动态代理的代码逻辑:
@Override
public Object getProxy(@Nullable 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);
// 该类实现了InvocationHandler接口,所以传this
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
InvocationHandler传递的就是JdkDynamicAopProxy本身,下面进入重点分析:
当我们执行:testBean.test();时,首先会进入到JdkDynamicAopProxy中的invoke方法中,
@Override
@Nullable
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MethodInvocation invocation;
Object oldProxy = null;
boolean setProxyContext = false;
// TargetSource中包含了原始类对象信息
TargetSource targetSource = this.advised.targetSource;
Object target = null;
try {
// 代码略。。。。对于equals,hashcode,方法进行处理
// Class类的isAssignableFrom(Class cls)方法:如果调用这个方法的class或接口 与 参数cls表示的类或接口相同,
// 或者是参数cls表示的类或接口的父类,则返回true。例如:
// System.out.println(ArrayList.class.isAssignableFrom(Object.class)); --> false
// System.out.println(Object.class.isAssignableFrom(ArrayList.class)); --> true
// 如果method所在类是Advised父类,则直接调用切点方法
else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
method.getDeclaringClass().isAssignableFrom(Advised.class)) {
// Service invocations on ProxyConfig with the proxy config...
return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
}
Object retVal;
// 对exposeProxy
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
// 将代理类对象proxy保存到ThreadLocal中,同时获取之前存储的oldProxy
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// Get as late as possible to minimize the time we "own" the target,
// in case it comes from a pool.
// 获取目标对象及类型
target = targetSource.getTarget();
Class> targetClass = (target != null ? target.getClass() : null);
// 获取当前方法的拦截器链(之前我们找的增强器统一封装成了拦截器链)
List
我们重点关注一下拦截器链的执行过程,首先,会将适配该方法对应的增强器封装成ReflectiveMethodInvocation,ReflectiveMethodInvocation中定义了一个currentInterceptorIndex 属性,该属性用于确定当前链路器执行到那个,
/**
* 实现拦截器的逐一调用
* @return
* @throws Throwable
*/
@Override
@Nullable
public Object proceed() throws Throwable {
// We start with an index of -1 and increment early.
// 执行完成所有增强方法后执行切点方法
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}
// 获取下一个要执行的拦截器
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
// Evaluate dynamic method matcher here: static part will already have been evaluated and found to match.
// 动态匹配
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 {
// Dynamic matching failed. Skip this interceptor and invoke the next in the chain.
// 不匹配不执行
return proceed();
}
}
else {
// It's an interceptor, so we just invoke it: The pointcut will have
// been evaluated statically before this object was constructed.
/* 普通拦截器,直接调用拦截器,比如:ExposeInvocationInterceptor、DelegatePerTargetObjectIntroductionInterceptor、
MethodBeforeAdviceInterceptor、AspectJAroundAdvice、AspectJAfterAdvice */
// 将this作为参数传递以保证当前实例中调用链的执行
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}
注意:在每一个增强器链路中,Spring都是会将链路的第一个链路器设置为ExposeInvocationInterceptor,该链路器的作用是存储对应MethodInvocation
我们会发现,在执行@After的AspectJAfterAdvice链路器的时候,首先会执行mi.proceed()方法,最终执行当前增前器对应的方法
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
try {
return mi.proceed();
}
finally {
invokeAdviceMethod(getJoinPointMatch(), null, null);
}
}
在执行@Around对应的AspectJAroundAdvice链路器时,其内部是回去对应的ProceedingJoinPoint,并执行当前增前器对应的增强方法
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
if (!(mi instanceof ProxyMethodInvocation)) {
throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
}
ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;
ProceedingJoinPoint pjp = lazyGetProceedingJoinPoint(pmi);
JoinPointMatch jpm = getJoinPointMatch(pmi);
return invokeAdviceMethod(pjp, jpm, null, null);
}
在执行@Before对应的AspectJMethodBeforeAdvice 链路器时,系统直接执行对应的增强方法
@Override
public void before(Method method, Object[] args, @Nullable Object target) throws Throwable {
// 调用通知方法
invokeAdviceMethod(getJoinPointMatch(), null, null);
}
我们在上面的一顿源码分析了之后,实际上,我们还是没有细聊关于@After,@Around,@Before,@AfterReturning等这几个切面的调度顺序,
实际上,在同时去使用@After,@Around,@Before这几个切面的时候,Spring首先是查询容器内的所有增强类(含有@AspectJ注解或者XML注入的增强器),并且,遍历所有的增强类中的增强方法(含有@After,@Around,@Before,@AfterReturning注解,这里以注解形似讲解),其中,在这里,Spring是对于查询当前类的增强方法进行了排序,排序的顺序关系正是:Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class
/**
* 获取增强方法
* @param aspectClass
* @return
*/
private List getAdvisorMethods(Class> aspectClass) {
final List methods = new ArrayList<>();
ReflectionUtils.doWithMethods(aspectClass, method -> {
// 声明为Pointcut的方法不处理
if (AnnotationUtils.getAnnotation(method, Pointcut.class) == null) {
methods.add(method);
}
});
// 增强方法排序
methods.sort(METHOD_COMPARATOR);
return methods;
}
private static final Comparator METHOD_COMPARATOR;
static {
Comparator adviceKindComparator = new ConvertingComparator<>(
new InstanceComparator<>(
Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class),
(Converter) method -> {
AspectJAnnotation> annotation =
AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(method);
return (annotation != null ? annotation.getAnnotation() : null);
});
Comparator methodNameComparator = new ConvertingComparator<>(Method::getName);
METHOD_COMPARATOR = adviceKindComparator.thenComparing(methodNameComparator);
}
因此:对于单个增前器内部的增强方法而言,AOP首先会优先执行环绕通知的处理方法,环绕通知中执行了对应的方法体本身的时候触发执行@Before方法,然后再执行环绕通知的下面的方法,最后再执行@After方法 最后再执行相关的AfterReturning和AfterThrowing 相关的增强方法
但是,对于多个增强器而言,如果A增强器(含有@AspectJ)和B增强器,对于方法a而言,A增强器中的@Before 增强器 的解析是优先于 B增强器的@Around的,情况就不一样了
例如:我们在ioc中只配置一个增强器(AspectJTest)
/**
* 假设是A增强器 。@Before
*
* @description:
* @project: spring
*/
@Aspect
public class AspectJTest {
@Pointcut("execution(* com.zcs.aop.usedemo.TestBean.test(..))")
public void test() {
}
@Pointcut("execution(* com.zcs.aop.usedemo.TestBean.subTest(..))")
public void subTest() {
}
@Before("test()")
public void beforeTest() {
System.out.println("A增强器 beforeTest");
}
@After("test()")
public void afterTest() {
System.out.println("A增强器 afterTest");
}
@Around("test() || subTest()")
public Object aroundTest(ProceedingJoinPoint proceedingJoinPoint) {
System.out.println("A增强器 =========beforeAroundTest==========");
Object o = null;
try {
o = proceedingJoinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("A增强器 =========afterAroundTest==========");
return o;
}
/**
* AfterReturning 增强处理可以访问到方法的返回值,但它不能改变目标方法的返回值
* @param rvt
* @return
*/
@AfterReturning(returning="rvt", pointcut="execution(* com.zcs.aop.usedemo.TestBean.afterReturn(..))")
public String afterRet(String rvt) {
return rvt+" wl";
}
}
此时,内部的执行逻辑正如我们上面所说的:
A增强器 =========beforeAroundTest==========
A增强器 beforeTest
TestBean内部自己执行====test
A增强器 =========afterAroundTest==========
A增强器 afterTest
现在我们尝试让A 增强器只含有一个@Before的增强方法,再增加一个B增强器,让B含有@Around的增强方法
A:
/**
* 假设是A增强器 。@Before
*
* @description:
* @project: spring
*/
@Aspect
public class AspectJTest {
@Pointcut("execution(* com.zcs.aop.usedemo.TestBean.test(..))")
public void test() {
}
@Pointcut("execution(* com.zcs.aop.usedemo.TestBean.subTest(..))")
public void subTest() {
}
@Before("test()")
public void beforeTest() {
System.out.println("A增强器 beforeTest");
}
// @After("test()")
// public void afterTest() {
// System.out.println("A增强器 afterTest");
// }
//
// @Around("test() || subTest()")
// public Object aroundTest(ProceedingJoinPoint proceedingJoinPoint) {
// System.out.println("A增强器 =========beforeAroundTest==========");
// Object o = null;
// try {
// o = proceedingJoinPoint.proceed();
// } catch (Throwable throwable) {
// throwable.printStackTrace();
// }
// System.out.println("A增强器 =========afterAroundTest==========");
// return o;
// }
//
// /**
// * AfterReturning 增强处理可以访问到方法的返回值,但它不能改变目标方法的返回值
// * @param rvt
// * @return
// */
// @AfterReturning(returning="rvt", pointcut="execution(* com.zcs.aop.usedemo.TestBean.afterReturn(..))")
// public String afterRet(String rvt) {
// return rvt+" wl";
// }
}
B:
/**
* B增强器 @Around 增强
*
* @author zhoucg
* @date 2020-03-18 14:15
*/
@Aspect
public class AspectJTestB {
@Pointcut("execution(* com.zcs.aop.usedemo.TestBean.test(..))")
public void test() {
}
@Around("test()")
public Object aroundTest(ProceedingJoinPoint proceedingJoinPoint) {
System.out.println("B增强器=========beforeAroundTest==========");
Object o = null;
try {
o = proceedingJoinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("B增强器=========afterAroundTest==========");
return o;
}
}
控制A增强器的加载优先于B增强器的加载,我们可以使用xml配置的方式:
此时,我们会发现,执行的结果是先执行了A增强器的@Before的增强方法,再执行了B增强器中的@Around方法
A增强器 beforeTest
B增强器=========beforeAroundTest==========
TestBean内部自己执行====test
B增强器=========afterAroundTest==========
对于这个问题,我在Spring的源码中并没有发现多个增强器的增强方法的收集最终同样按照这个进行排序
private static final Comparator METHOD_COMPARATOR;
static {
Comparator adviceKindComparator = new ConvertingComparator<>(
new InstanceComparator<>(
Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class),
(Converter) method -> {
AspectJAnnotation> annotation =
AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(method);
return (annotation != null ? annotation.getAnnotation() : null);
});
Comparator methodNameComparator = new ConvertingComparator<>(Method::getName);
METHOD_COMPARATOR = adviceKindComparator.thenComparing(methodNameComparator);
}
所以出现了这个问题,不知道这个算不算Spring的一个小小的特别之处,或者是一个bug的呢,我觉得应该不是BUG,可能是提醒我们对于同一个方法的增强方法最好是定义到一个类上吧