原创作者: 计文柯   阅读:5097次   评论:0条   更新时间:2011-05-26    

《Spring技术内幕——深入解析Spring架构与设计原理》


 
书名:Spring技术内幕——深入解析Spring架构与设计原理
作者:计文柯
ISBN:9787111288060
丛书名:揭秘系列丛书
出版社:机械工业出版社
出版日期:2010 年1月
开本:16
页码:300
版次:1-1
定价:55元
豆瓣网讨论地址:http://www.douban.com/subject/4199483/
China-pub预订地址:http://www.china-pub.com/196261



图 书 内 容


    本书是Spring领域的问鼎之作,由业界拥有10余年开发经验的资深Java专家亲自执笔!Java开发者社区和Spring开发者社区一致强烈推荐。


    国内第一本基于Spring 3.0的著作,从源代码的角度对Spring的内核和各个主要功能模块的架构、设计和实现原理进行了深入剖析。你不仅能从本书中参透Spring框架的优秀架构和设计思想,而且还能从Spring优雅的实现源码中一窥Java语言的精髓。此外,本书还展示了阅读源代码的卓越方法,不仅授你以鱼,而且还授你以渔!


    如果你以一种淡定的心态翻开这本书,无论你是Java程序员、Spring开发者,还是平台开发人员、系统架构师,抑或是对开源软件源代码着迷的代码狂人,都能从本书中受益。

 

 

(续前一篇文章)

3.3.4  AOP拦截器链的调用
在了解了对目标对象的直接调用以后,我们开始进入AOP实现的核心部分了,对于AOP是怎样完成对目标对象的增强的,这些实现是封装在AOP拦截器链中,由一个个具体的拦截器来完成的。

尽管我们在上面看到,使用JDK和CGLIB会生成不同的AopProxy代理对象,从而构造了不同的回调方法来启动对拦截器链的调用,比如在JdkDynamicAopProxy中的invoke方法,以及Cglib2AopProxy中使用DynamicAdvisedInterceptor的intercept方法。它们都使用了不同的AopProxy代理对象,但最终对AOP拦截的处理可谓殊途同归:它们对拦截器链的调用都是在ReflectiveMethodInvocation中通过proceed方法实现的。在这个proceed方法里,会逐个运行拦截器的拦截方法。在运行拦截器的拦截方法之前,需要对代理方法完成一个匹配判断,通过这个匹配判断来决定拦截器是否满足切面增强的要求。大家一定还记得,我们前面提到的,在Pointcut切点中需要进行matches的匹配过程,就是这个matches调用对方法进行匹配判断,来决定是否需要实行通知增强;以下看到的调用就是进行matches的地方,具体的处理过程在ReflectiveMethodInvocation的proceed方法中,如代码清单3-21所示。在proceed方法中先进行判断,如果现在已经运行到拦截器链的末尾,那么就会直接调用目标对象的实现方法;否则,沿着拦截器链继续进行,得到下一个拦截器,通过这个拦截器进行matches判断,判断是否适用于横切增强的场合,如果是,从拦截器中得到通知器,并启动通知器的invoke方法进行切面增强。在这个过程结束以后,会迭代调用proceed方法,直到拦截器链中的拦截器都完成以上的拦截过程为止。


代码清单3-21  拦截器的运行

public Object proceed() throws Throwable {
 // We start with an index of -1 and increment early.
 /**
 *如果拦截器链中的拦截器迭代调用完毕,这里开始调用target的函数,
 *这个函数是通过反射机制完成的,具体实现在:AopUtils.invokeJoinpointUsingReflection方法里面。
 */
 if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethod
Matchers.size() - 1) {
  return invokeJoinpoint();
 }
 //这里沿着定义好的 interceptorOrInterceptionAdvice链进行处理。
 Object interceptorOrInterceptionAdvice =
 this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptor
Index);
 if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamic
MethodMatcher) {
  /**
  * Evaluate dynamic method matcher here: static part will already have
  * been evaluated and found to match.
  */
  /**
  *这里对拦截器进行动态匹配的判断,还记得我们前面分析的pointcut吗?
  *这里是触发进行匹配的地方,如果和定义的pointcut匹配,那么这个advice将会得到执行。
  */
  InterceptorAndDynamicMethodMatcher dm =
      (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
  if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
   return dm.interceptor.invoke(this);
  }
  else {
   // Dynamic matching failed.
   // Skip this interceptor and invoke the next in the chain.
   // 如果不匹配,那么proceed会被递归调用,直到所有的拦截器都被运行过为止。
   return proceed();
  }
 }
 else {
  /**
  * It's an interceptor, so we just invoke it: The pointcut will have
  * been evaluated statically before this object was constructed.
  */
  //如果是interceptor,直接调用这个interceptor对应的方法。
  return((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
 }
}

 

以上就是整个拦截器及target目标对象方法被调用的过程。小荷才露尖尖角,我们已经在这里看到对advice通知的调用入口了,虽然这个大名鼎鼎的advice到现在还没有完全现身,但我们已经看到了它的运行轨迹;我们先提出一个疑问来提提大家的兴趣:这些advisor是怎样从配置文件中获得并配置到proxy的拦截器链中去的?我们平常使用的advice通知是怎样起作用的?这些都是了解AOP实现原理的重要问题,下面我们就这些问题已经展示的线索继续展开分析,去寻求这些问题的答案吧。


3.3.5  配置通知器
在整个AopProxy代理对象的拦截回调过程中,让我们先回到ReflectiveMethod
Invocation类的proceed方法,在这个方法里,可以看到得到了配置的interceptor-
OrInterceptionAdvice,如下所示。

Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);

 

这个interceptorOrInterceptionAdvice是获得的拦截器,它通过拦截器机制对目标对象的行为增强起作用。这个拦截器来自于interceptorsAndDynamicMethodMatchers,具体来说,它是interceptorsAndDynamicMethodMatchers持有的List中的一个元素。关于如何配置拦截器的问题,就被转化为这个List中的拦截器元素是从哪里来、在哪里配置的问题。我们接着对invoke调用进行回放,回到JdkDynamicAopProxy中的invoke方法中,可以看到这个List中的interceptors是在哪个调用中获取的。对于Cglib2AopProxy,也有类似的过程,只不过这个过程是在DynamicAdvisedInterceptor的intercept回调中实现的,如下所示。

List<Object> chain =
this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

 

在上面的代码中可以看到,获取interceptors的操作是由advised对象完成的,这个advised是一个AdvisedSupport对象,从类的继承关系上看,这个AdvisedSupport类同时也是ProxyFactoryBean的基类。从AdvisedSupport的代码里可以看到 getInterceptorsAndDynamic
InterceptionAdvice的实现,如代码清单3-22所示。在这个方法里取得了拦截器链,在取得拦截器链的时候,为提高取得拦截器链的效率,还为这个拦截器链设置了缓存。


代码清单3-22  AdvisedSupport取得拦截器

public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method,
Class targetClass) {
 /**
 *这里使用了cache,会从cache去取已有的inteceptor链,但是第一次
 *还是需要自己动手生成的。这个inteceptor链的生成是由 advisorChainFactory完成的,
 *在这里使用的是DefaultAdvisorChainFactory。
 */
 MethodCacheKey cacheKey = new MethodCacheKey(method);
 List<Object> cached = this.methodCache.get(cacheKey);
 if (cached == null) {
  cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
    this, method, targetClass);
  this.methodCache.put(cacheKey, cached);
 }
 return cached;
}

 

取得拦截器链的工作是由配置好的advisorChainFactory 来完成的,从名字上可以猜到,它是一个生成通知器链的工厂;在这里,advisorChainFactory 被配置成一个DefaultAdvisorChainFactory对象,在DefaultAdvisorChainFactory中实现了interceptor链的获取过程,如代码清单3-23所示。在这个获取过程中,首先设置了一个List,其长度是由配置的通知器的个数来决定的,这个配置就是我们在XML中对ProxyFactoryBean做的interceptNames属性的配置;然后,DefaultAdvisorChain-
Factory会通过一个AdvisorAdapterRegistry来实现拦截器的注册,在后面我们会看到,这个AdvisorAdapterRegistry对advice通知的织入功能起了很大的作用,关于这个AdvisorAdapterRegistry对象的实现原理,我们会在下面分析通知是如何实现增强的部分进行详细阐述。有了这个AdvisorAdapterRegistry注册器,由它来对从ProxyFactoryBean配置中得到的通知进行适配,从而获得相应的拦截器,再把它加入到前面设置好的List中去,完成这个所谓的拦截器注册过程。在这些拦截器适配和注册过程完成以后,这个List中的拦截器会被JDK生成的AopProxy代理对象的invoke方法,或者CGLIB代理对象的intercept拦截方法取得,并启动拦截器的invoke调用,最终触发通知的切面增强。

代码清单3-23  DefaultAdvisorChainFactory生成拦截器链

public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
  Advised config, Method method, Class targetClass) {

 /**
 * This is somewhat tricky... we have to process introductions first,
 * but we need to preserve order in the ultimate list.
 */
 //advisor链已经在config中持有了,这里我们可以直接使用。
 List<Object> interceptorList = new ArrayList<Object>
(config.getAdvisors().length);
 boolean hasIntroductions = hasMatchingIntroductions(config, targetClass);
 AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
 for (Advisor advisor : config.getAdvisors()) {
   if (advisor instanceof PointcutAdvisor) {
   // Add it conditionally.
   PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
   if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClass
Filter().matches(targetClass)) {
/**
*拦截器链是通过AdvisorAdapterRegistry来加入的,这个AdvisorAdapterRegistry
*对advice织入起了很大的作用,在后面的分析中会看到。
*/
MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
 MethodMatcher mm = pointcutAdvisor.getPointcut().gEtMethodMatcher();
//使用MethodMatchers的matches方法进行匹配判断。
    if (MethodMatchers.matches(mm, method, targetClass, hasIntroductions)) {
     if (mm.isRuntime()) {
     /**
     * Creating a new object instance in the getInterceptors()method
     * isn't a problem as we normally cache created chains.
     */
      for (MethodInterceptor interceptor : interceptors) {
       interceptorList.add(new InterceptorAndDynamicMethod
      Matcher(interceptor, mm));
      }
     }
     else {
       interceptorList.addAll(Arrays.asList(interceptors));
     }
    }
   }
  }
  else if (advisor instanceof IntroductionAdvisor) {
   IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
   if (config.isPreFiltered() || ia.getClassFilter().matches(targetClass)) {
    Interceptor[] interceptors = registry.getInterceptors(advisor);
    interceptorList.addAll(Arrays.asList(interceptors));
   }
  }
  else {
   Interceptor[] interceptors = registry.getInterceptors(advisor);
   interceptorList.addAll(Arrays.asList(interceptors));
  }
 }
 return interceptorList;
}
// Determine whether the Advisors contain matching introductions.
private static boolean hasMatchingIntroductions(Advised config, ClasstargetClass) {
 for (int i = 0; i < config.getAdvisors().length; i++) {
  Advisor advisor = config.getAdvisors()[i];
  if (advisor instanceof IntroductionAdvisor) {
   IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
   if (ia.getClassFilter().matches(targetClass)) {
    return true;
   }
  }
 }
 return false;
}

 

事实上,这里的advisor通知器是从AdvisorSupport中取得的,从对它调用过程上看会非常地清楚,如图3-10所示。
在ProxyFactoryBean的getObject方法中对advisor进行初始化的时候,从XML配置中获取了advisor通知器。在ProxyFactoryBean中,我们看看对advisor进行初始化的代码实现,如代码清单3-24所示。在这个初始化的advisor的取得中,可以看到对IoC容器的一个getBean回调,由这个对IoC容器的getBean调用来得到配置好的advisor通知器。



 

图3-10  Advisor的调用过程


代码清单3-24  在拦截器链的初始化中获取advisor通知器

private synchronized void initializeAdvisorChain() throws AopConfigException,
BeansException {
 if (this.advisorChainInitialized) {
  return;
 }
 if (!ObjectUtils.isEmpty(this.interceptorNames)) {
  if (this.beanFactory == null) {
   throw new IllegalStateException("No BeanFactory available anymore
   (probably due to serialization) " +
   "- cannot resolve interceptor names " + Arrays.asList(this.
  interceptorNames));
  }
  // Globals can't be last unless we specified a targetSource using the
property...
  if (this.interceptorNames[this.interceptorNames.length - 1].endsWith
(GLOBAL_SUFFIX) &&
    this.targetName == null && this.targetSource == EMPTY_TARGET_
SOURCE) {
   throw new AopConfigException("Target required after globals");
  }
  // Materialize interceptor chain from bean names.
  for (String name : this.interceptorNames) {
   if (logger.isTraceEnabled()) {
    logger.trace("Configuring advisor or advice '" + name + "'");
   }
   if (name.endsWith(GLOBAL_SUFFIX)) {
    if (!(this.beanFactory instanceof ListableBeanFactory)) {
     throw new AopConfigException(
       "Can only use global advisors or interceptors with
      a ListableBeanFactory");
    }
    addGlobalAdvisor((ListableBeanFactory) this.beanFactory,
      name.substring(0, name.length() - GLOBAL_SUFFIX.length()));
   }
   else {
    // If we get here, we need to add a named interceptor.
    // We must check if it's a singleton or prototype.
    Object advice;

    if (this.singleton || this.beanFactory.isSingleton(name)) {
    // Add the real Advisor/Advice to the chain.
    /**
    * 这里是取得advisor的地方,是通过beanFactory取得的,
    *把interceptorNames这个List中的interceptor名字交给
    *beanFactory就可以了,然后通过调用BeanFactory的getBean去获取。
    */
     advice = this.beanFactory.getBean(name);
    }
    else {
     // It's a prototype Advice or Advisor: replace with a prototype.
     /**
     * Avoid unnecessary creation of prototype bean just for
      *advisor chain initialization.
     */
     advice = new PrototypePlaceholderAdvisor(name);
    }
    addAdvisorOnChainCreation(advice, name);
   }
  }
 }
 this.advisorChainInitialized = true;
}

 

advisor通知器的取得是由IoC容器完成的,但是在ProxyFactoryBean中是如何获得IoC容器,然后通过回调IoC容器的getBean方法来得到需要的通知器advisor呢?这涉及IoC容器的实现原理,在我们使用DefaultListableBeanFactory作为IoC容器使用的时候,由于它的基类是AbstractAutowireCapableBeanFactory,在这个AbstractAutowireCapable-
BeanFactory中,可以看到一个对Bean进行初始化的initializeBean方法;在这个Bean的初始化过程中,对IoC容器在Bean中的回调进行了设置,首先,判断这个Bean的类型是不是实现了BeanFactoryAware接口,如果它实现了BeanFactoryAware接口,那么它就一定实现了BeanFactoryAware定义的接口方法,通过这个接口方法,可以把IoC容器设置到Bean自身定义的一个属性中去。这样,在这个Bean的自身实现中,就能够得到它所在的IoC容器,从而调用IoC容器的getBean方法,完成对IoC容器的回调,就像一个有特异功能的Bean一样, 除了使用为自己设计的功能之外,还可以去调用它所在的容器的功能,如下所示。

if (bean instanceof BeanFactoryAware) {
  ((BeanFactoryAware) bean).setBeanFactory(this);
}

 

对IoC容器的使用,如果需要回调容器,前提是当前的Bean需要实现BeanFactoryAware接口,这个接口只需要实现一个接口方法setBeanFactory,同时设置一个属性来持有BeanFactory的IoC容器,就可以在Bean中取得IoC容器进行回调了。在IoC容器对Bean进行初始化的时候,会对Bean的类型进行判断,如果这是一个BeanFactoryAware的Bean类型,那么IoC容器会调用这个Bean的setBeanFactory方法,完成对这个BeanFactory在Bean中的设置。具体来说,对于ProxyFactoryBean,它实现了这个接口,所以在它初始化完成以后,可以在Bean中使用容器进行回调。这里设置进去的this对象就是Bean所在的IoC容器,一般而言是DefaultListableBeanFactory对象。通过这个设置,在得到这个设置好的BeanFactory以后,ProxyFactoryBean就可以通过回调容器的getBean去获取配置在Bean定义文件中的通知器了,获取通知器就是向IoC容器getBean的过程。了解IoC容器的实现原理的读者都知道,这个getBean是IoC容器一个非常基本的方法。在调用时,ProxyFactoryBean需要给出通知器的名字,而这些名字都是在interceptorNames的List中已经配置好的,在IoC对FactoryBean进行依赖注入时,会直接注入到FactoryBean的interceptorNames属性中。完成这个过程以后,ProxyFactoryBean就获得了配置的通知器,为切面增强做好准备。


3.3.6  Advice通知的实现
经过前面的分析,我们看到在AopProxy代理对象的生成的时候,AopProxy代理对象的拦截器也同样建立起来了,对于拦截器的拦截调用和最终目标对象的方法调用,也都看到了它们相应的实现原理。但是,对于AOP实现的重要部分,Spring AOP定义的通知是怎样实现对目标对象的增强的呢?本节将探讨这个问题。在为AopProxy代理对象配置拦截器的实现中,有一个取得拦截器的配置过程,这个过程是由DefaultAdvisorChainFactory实现的,而在这个工厂类中,它负责生成拦截器链,在它的getInterceptorsAndDynamicInterceptionAdvice方法中,有一个适配和注册过程,就是在这个适配和注册过程中,通过配置Spring预先设计好的拦截器,Spring加入了它对AOP实现的处理。为了解这个详细的过程,我们先从DefaultAdvisor-
ChainFactory的实现开始,如代码清单3-25所示。在DefaultAdvisorChainFactory的实现中,首先构造了一个GlobalAdvisorAdapterRegistry单件,然后,对配置的Advisor通知器逐个进行遍历,这些通知器链都是配置在interceptorNames中的,这点我们已经不陌生;从getInterceptorsAndDynamicInterceptionAdvice传递进来的advised参数对象中可以方便地取到配置的通知器,有了这些通知器,接着就是一个由GlobalAdvisorAdapter-
Registry来完成的拦截器的适配和注册过程。


代码清单3-25  DefaultAdvisorChainFactory使用GlobalAdvisorAdapterRegistry得到AOP拦截器

//得到注册器GlobalAdvisorAdapterRegistry,这是一个单件模式的实现。
AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
for (Advisor advisor : config.getAdvisors()) {
 if (advisor instanceof PointcutAdvisor) {
  // Add it conditionally.
  PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
  if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClass
 Filter().matches(targetClass)) {
   //从GlobalAdvisorAdapterRegistry中取得MethodInterceptor的实现。
   MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
   MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
   if (MethodMatchers.matches(mm, method, targetClass, hasIntroductions)) {
    if (mm.isRuntime()) {
     /**
     * Creating a new object instance in the getInterceptors() method
     * isn't a problem as we normally cache created chains.
     */
     for (MethodInterceptor interceptor : interceptors) {
      interceptorList.add(new InterceptorAndDynamicMethod
     Matcher(interceptor, mm));

     }
    }
    else {
interceptorList.addAll(Arrays.asList(interceptors));
     }
    }
   }
  }

 

仔细揣摩了以上代码的读者一定会注意到,在这个GlobalAdvisorAdapterRegistry中隐藏着不少AOP实现的重要细节,它的getInterceptors方法为AOP实现作出了很大的贡献,就是这个方法封装着advice织入实现的入口,我们先从GlobalAdvisorAdapterRegistry的实现入手,如代码清单3-26所示。从代码上看,GlobalAdvisorAdapterRegistry的实现很简洁,起到的基本上是一个适配器的作用,但同时也是一个单件模式的应用,它为Spring AOP模块提供了一个DefaultAdvisorAdapterRegistry单件,这个DefaultAdvisorAdapter
Registry是我们后面要分析的重点,像它的名字一样,由它来完成各种通知的适配和注册工作。


代码清单3-26  GlobalAdvisorAdapterRegistry的实现

public abstract class GlobalAdvisorAdapterRegistry {
 /**
 * Keep track of a single instance so we can return it to classes that request it.
 */
 private static final AdvisorAdapterRegistry instance = new DefaultAdvisor
AdapterRegistry();
 
 // Return the singleton DefaultAdvisorAdapterRegistry instance.
 public static AdvisorAdapterRegistry getInstance() {
  return instance;
 }
}

 

到这里,神秘的面纱慢慢地开始被揭开了,在DefaultAdvisorAdapterRegistry中设置了一系列的adapter适配器,正是这些adapter适配器的实现,为Spring AOP的advice提供编织能力,让我们先到DefaultAdvisorAdapterRegistry中去看一看究竟在这里发生了什么,如代码清单3-27所示。首先,我们看到了一系列在我们的AOP应用中,与使用到的Spring AOP的advice通知相对应的adapter适配实现,并看到了对这些adapter的具体使用。具体地说,对它们的使用主要体现在两个方面,一是调用adapter的support方法,通过这个方法来判断取得的advice属于什么类型的advice通知,从而根据不同的advice类型来注册不同的AdviceInterceopter,也就是我们前面看到的那些拦截器;而另一方面,对于这些AdviceInterceopter,不需要我们操心,都是Spring AOP框架设计好了的,它们是为实现不同的advice功能提供服务的。有了这些AdviceInterceopter,我们可以方便地使用由Spring提供的各种不同的advice来设计AOP应用。也就是说,正是这些AdviceInterceptor最终实现了advice通知在AopProxy代理对象中的织入功能。

代码清单3-27  DefaultAdvisorAdapterRegistry的实现

public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry,
Serializable {
 //持有AdvisorAdaptor的List,它的Adapter是与实现Spring AOP的advice增强功能相对应的。
 private final List<AdvisorAdapter> adapters = new ArrayList<AdvisorAdapter>(3);

 // Create a new DefaultAdvisorAdapterRegistry, registering well-known adapters.
 /**
 *这里把已有的advice实现的Adapter加入进来,有我们非常熟悉的
 *MethodBeforeAdvice、AfterReturningAdvice、ThrowsAdvice这些AOP的advice封装实现。
 */
 public DefaultAdvisorAdapterRegistry() {
  registerAdvisorAdapter(new MethodBeforeAdviceAdapter());
  registerAdvisorAdapter(new AfterReturningAdviceAdapter());
  registerAdvisorAdapter(new ThrowsAdviceAdapter());
 }
 public Advisor wrap(Object adviceObject) throws UnknownAdviceTypeException {
  if (adviceObject instanceof Advisor) {
   return (Advisor) adviceObject;
  }
  if (!(adviceObject instanceof Advice)) {
   throw new UnknownAdviceTypeException(adviceObject);
  }
  Advice advice = (Advice) adviceObject;
  if (advice instanceof MethodInterceptor) {
   // So well-known it doesn't even need an adapter.
   return new DefaultPointcutAdvisor(advice);
  }
  for (AdvisorAdapter adapter : this.adapters) {
   // Check that it is supported.
   if (adapter.supportsAdvice(advice)) {
    return new DefaultPointcutAdvisor(advice);
   }
  }
  throw new UnknownAdviceTypeException(advice);
 }
 //这里是在DefaultAdvisorChainFactory中启动的getInteceptors方法。
 public MethodInterceptor[] getInterceptors(Advisor advisor) throwsUnknownAdvice
TypeException {
  List<MethodInterceptor> interceptors = new ArrayList<MethodInterceptor>(3);
  //从Advisor通知器配置中取得advice通知。
  Advice advice = advisor.getAdvice();
  //如果通知是MethodInterceptor类型的通知,直接加入interceptors的List中,不需要适配。
  if (advice instanceof MethodInterceptor) {
   interceptors.add((MethodInterceptor) advice);
  }
  /**
  *对通知进行适配,使用已经配置好的Adapter:MethodBeforeAdviceAdapter,
  *AfterReturningAdviceAdapter以及ThrowsAdviceAdapter。
  *然后从对应的adapter中取出封装好AOP编织功能的拦截器。
  */
  for (AdvisorAdapter adapter : this.adapters) {
   if (adapter.supportsAdvice(advice)) {
    interceptors.add(adapter.getInterceptor(advisor));
   }
  }
  if (interceptors.isEmpty()) {
   throw new UnknownAdviceTypeException(advisor.getAdvice());
  }
  return interceptors.toArray(new MethodInterceptor[interceptors.size()]);
 }
 public void registerAdvisorAdapter(AdvisorAdapter adapter) {
  this.adapters.add(adapter);
 }
}

 

在DefaultAdvisorAdapterRegistry的getInterceptors调用中,我们可以看到MethodBeforeAdviceAdapter、AfterReturningAdviceAdapter以及ThrowsAdvice
Adapter这几个通知适配器,从名字上可以看到,它们完全是和advice一一对应的,在这里,它们作为适配器被加入到adaper的List中来了。换一个角度,从这几个类的设计层次和关系上看,它们都是实现AdvisorAdapter接口的同一层次的类,只是各自承担着不同的适配任务,一对一地服务于不同的advice实现。它们的类层次关系如图3-11所示。



 

图3-11  AdvisorAdapter接口及其实现


我们举MethodBeforeAdviceAdapter为例,看看它的具体实现,如代码清单3-28所示。这个MethodBeforeAdviceAdapter的实现并不复杂,它实现了AdvisorAdapter的两个接口方法,一个是supportsAdvice,它对advice的类型进行判断,如果advice是MethodBeforeAdvice的实例,那么返回值为true;另一个是对getInterceptor接口方法的实现,它把advice通知从通知器中取出,然后创建一个MethodBeforeAdviceInterceptor对象,通过这个MethodBeforeAdviceInterceptor对象把取得的advice通知包装起来,然后返回。

代码清单3-28  MethodBeforeAdviceAdapter的实现

class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {
 public boolean supportsAdvice(Advice advice) {
  return (advice instanceof MethodBeforeAdvice);
 }
 public MethodInterceptor getInterceptor(Advisor advisor) {
  MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
  return new MethodBeforeAdviceInterceptor(advice);
 }
}

 

到这里就非常清楚了,Spring AOP为了实现advice的织入,设计了特定的拦截器对这些功能进行了封装,虽然应用不会直接用到这些拦截器,但却是advice发挥作用的必不可少的基础设施。接着这条线索,我们还是使用MethodBeforeAdviceInterceptor作为例子,看看它是怎样完成对advice的封装的,如代码清单3-29所示。这个MethodBeforeAdviceInterceptor完成的是对MethodBeforeAdvice通知的封装,可以在MethodBeforeAdviceInterceptor设计的invoke回调方法中看到首先触发了advice的before回调,然后才是MethodInvocation的proceed方法调用。看到这里,就已经和前面我们在ReflectiveMethodInvocation中的proceed分析中联系起来了。在AopProxy代理对象触发的ReflectiveMethodInvocation的proceed方法中,在取得拦截器以后,启动了对拦截器invoke方法的调用。按照AOP的配置规则,ReflectiveMethodInvocation触发的拦截器invoke方法,最终会根据不同的advice类型,触发Spring对不同的advice的拦截器封装,比如对MethodBeforeAdvice,就会最终触发MethodBeforeAdviceInterceptor的invoke方法。在这个MethodBefore-
AdviceInterceptor方法中,先调用advice的before方法,即MethodBeforeAdvice所需要的对目标对象的增强效果:在方法调用之前完成通知增强。


代码清单3-29  MethodBeforeAdviceInterceptor的实现

public class MethodBeforeAdviceInterceptor implements MethodInterceptor, Serializable {
 private MethodBeforeAdvice advice;
 /**
  * Create a new MethodBeforeAdviceInterceptor for the given advice.
  * @param advice the MethodBeforeAdvice to wrap
  */
 public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
  Assert.notNull(advice, "Advice must not be null");
  this.advice = advice;
 }
 //这个invoke方法是拦截器的回调方法,会在代理对象的方法被调用的时候触发回调。
 public Object invoke(MethodInvocation mi) throws Throwable {
  this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis() );
  return mi.proceed();
 }
}

 

了解了MethodBeforeAdviceInterceptor的实现原理,对于其他的advice的实现也是可以举一反三的,比如对于AfterReturningAdviceInterceptor的实现,它和MethodBefore-
AdviceInterceptor实现不同的地方,就是在AfterReturningAdviceInterceptor的invoke方法中,先完成了MethodInvocation的proceed调用,也就是目标对象的方法调用,然后再启动advice的afterReturning回调,这些实现原理在代码中可以很清楚地看到,如代码清单3-30所示。


代码清单3-30  AfterReturningAdviceInterceptor的实现

public class AfterReturningAdviceInterceptor implements MethodInterceptor,
AfterAdvice,Serializable {
 private final AfterReturningAdvice advice;
 /**
  * Create a new AfterReturningAdviceInterceptor for the given advice.
  * @param advice the AfterReturningAdvice to wrap
  */
 public AfterReturningAdviceInterceptor(AfterReturningAdvice advice) {
  Assert.notNull(advice, "Advice must not be null");
  this.advice = advice;
 }
 public Object invoke(MethodInvocation mi) throws Throwable {
  Object retVal = mi.proceed();
  this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(),
 mi.getThis());
  return retVal;
 }
}

 

对于ThrowAdvice的实现原理,和上面两种情况是非常类似的,也是封装在对应的AdviceInterceptor中实现的,如代码清单3-31所示。只是相对于MethodBeforeAdvice和AfterReturningAdvice的回调方法调用,ThrowAdvice的回调方法调用比前两者的实现要复杂一些;它维护了exceptionHandlerMap来对应不同的方法调用场景,这个exceptionHandlerMap中handler的取得,是与触发ThrowAdvise增强的异常相关的。

代码清单3-31  ThrowsAdviceInterceptor的实现

public class ThrowsAdviceInterceptor implements MethodInterceptor, AfterAdvice {
 private static final String AFTER_THROWING = "afterThrowing";
 private static final Log logger = LogFactory.getLog(ThrowsAdviceInterceptor.
class);
 private final Object throwsAdvice;
 // Methods on throws advice, keyed by exception class.
 private final Map<Class, Method> exceptionHandlerMap = new HashMap<Class,
Method>();
 public ThrowsAdviceInterceptor(Object throwsAdvice) {
  Assert.notNull(throwsAdvice, "Advice must not be null");
  this.throwsAdvice = throwsAdvice;
  //配置ThrowsAdvice的回调方法。
  Method[] methods = throwsAdvice.getClass().getMethods();
  for (Method method : methods) {
   if (method.getName().equals(AFTER_THROWING) &&
    (method.getParameterTypes().length == 1 || method.getParameter
   Types().length == 4) &&
 Throwable.class.isAssignableFrom(method.getParameterTypes()[method.getParame
terTypes().length - 1])
    ) {
    // Have an exception handler.
 this.exceptionHandlerMap.put(method.getParameterTypes()
 [method.getParameterTypes().length - 1], method);
    if (logger.isDebugEnabled()) {
     logger.debug("Found exception handler method: " + method);
    }
   }
  }
  if (this.exceptionHandlerMap.isEmpty()) {
   throw new IllegalArgumentException(
     "At least one handler method must be found in class [" +
    throwsAdvice.getClass() + "]");
  }
 }
 public int getHandlerMethodCount() {
  return this.exceptionHandlerMap.size();
 }
 private Method getExceptionHandler(Throwable exception) {
  Class exceptionClass = exception.getClass();
  if (logger.isTraceEnabled()) {
   logger.trace("Trying to find handler for exception of type [" +
  exceptionClass.getName() + "]");
  }
  Method handler = this.exceptionHandlerMap.get(exceptionClass);
  while (handler == null && !exceptionClass.equals(Throwable.class)) {
   exceptionClass = exceptionClass.getSuperclass();
   handler = this.exceptionHandlerMap.get(exceptionClass);
  }
  if (handler != null && logger.isDebugEnabled()) {
   logger.debug("Found handler for exception of type [" + 
  exceptionClass.getName() + "]: " + handler);
  }
  return handler;
 }
 public Object invoke(MethodInvocation mi) throws Throwable {
  /**
  *把对目标对象的方法调用放入try/catch中,并在catch中触发ThrowAdvice的回调,
  *然后把异常接着向外抛出,不做过多的处理。
  */
  try {
   return mi.proceed();
  }
  catch (Throwable ex) {
   Method handlerMethod = getExceptionHandler(ex);
   if (handlerMethod != null) {
    invokeHandlerMethod(mi, ex, handlerMethod);
   }
   throw ex;
  }
 }
 //通过反射启动对ThrowAdvice回调方法的调用。
 private void invokeHandlerMethod(MethodInvocation mi, Throwable ex, Method
method) throws Throwable {
  Object[] handlerArgs;
  if (method.getParameterTypes().length == 1) {
   handlerArgs = new Object[] { ex };
  }
  else {
   handlerArgs = new Object[] {mi.getMethod(), mi.getArguments(), mi.
  getThis(), ex};
  }
  try {
   method.invoke(this.throwsAdvice, handlerArgs);
  }
  catch (InvocationTargetException targetEx) {
   throw targetEx.getTargetException();
  }
 }
}

 

3.3.7  ProxyFactory实现AOP
我们回到3.2.1节中提到的Spring AOP的类层次关系,从中可以看到除了使用ProxyFactoryBean实现AOP应用之外,还可以使用ProxyFactory来实现Spring AOP的功能,只是在使用ProxyFactory的时候,需要编程式的完成AOP应用的设置。我们举一个使用ProxyFactory的例子,如代码清单3-32所示。

代码清单3-32  ProxyFactory的使用

TargetImpl target = new TargetImpl();
ProxyFactory aopFactory = new ProxyFactory(target);
aopFactory.addAdvisor(yourAdvisor);
aopFactory.addAdvice(yourAdvice);
TargetImpl targetProxy = (TargetImpl)aopFactory.getProxy();

 

对于使用ProxyFactory实现AOP功能,它的实现原理与ProxyFactoryBean的实现原理是一样的,只是在最外层的表现形式上有所不同;ProxyFactory没有使用FactoryBean的IoC封装,而是通过直接继承ProxyCreatorSupport的功能来完成AOP的属性配置。至于其他ProxyCreatorSupport的子类,ProxyFactory取得AopProxy代理对象其实是和ProxyFactoryBean是一样的。一般来说,也是通过getProxy为入口,由DefaultAopProxy-
Factory来完成的,关于取得AopProxy的详细分析和以后对拦截器调用的实现原理,前面我们都分析过了,这里就不再重复了。对ProxyFactory实现感兴趣的读者,可以从以下的源代码中看到它与ProxyFactoryBean实现上不同的地方,ProxyFactory的实现代码如代码清单3-33所示。从代码清单上可以看到在ProxyFactory的getProxy方法,由这个方法去取得AopProxy代理对象,这个getProxy方法的实现使用了ProxyFactory的基类ProxyCreatorSupport的createProxy方法来生成AopProxy代理对象,而这个AopProxy代理对象的生成是由AopProxyFactory来完成的,它会生成JDK或者CGLIB的代理对象。从这里的getProxy的实现开始,ProxyFactory和ProxyFactoryBean在AOP的功能实现上基本上都是一样的,包括以后拦截器的调用等。


代码清单3-33  ProxyFactory的实现

public class ProxyFactory extends ProxyCreatorSupport {
 public ProxyFactory() {
 }
 public ProxyFactory(Object target) {
  Assert.notNull(target, "Target object must not be null");
  setInterfaces(ClassUtils.getAllInterfaces(target));
  setTarget(target);
 }
 public ProxyFactory(Class[] proxyInterfaces) {
  setInterfaces(proxyInterfaces);
 }
public ProxyFactory(Class proxyInterface, Interceptor interceptor) {
  addInterface(proxyInterface);
  addAdvice(interceptor);
 }
public ProxyFactory(Class proxyInterface, TargetSource targetSource) {
  addInterface(proxyInterface);
  setTargetSource(targetSource);
 }
 public <T> T getProxy() {
  return (T) createAopProxy().getProxy();
 }
 public <T> T  getProxy(ClassLoader classLoader) {
  return (T) createAopProxy().getProxy(classLoader);
 }
 public static Object getProxy(Class proxyInterface, Interceptor interceptor) {
  return new ProxyFactory(proxyInterface, interceptor).getProxy();
 }
 public static Object getProxy(Class proxyInterface, TargetSource targetSource) {
  return new ProxyFactory(proxyInterface, targetSource).getProxy();
 }
public static Object getProxy(TargetSource targetSource) {
  if (targetSource.getTargetClass() == null) {
   throw new IllegalArgumentException("Cannot create class proxy for
TargetSource with null target class");
  }
  ProxyFactory proxyFactory = new ProxyFactory();
  proxyFactory.setTargetSource(targetSource);
  proxyFactory.setProxyTargetClass(true);
  return proxyFactory.getProxy();
 }
}

 

3.4  Spring AOP的高级特性
了解了Spring AOP的基本实现,下面我们来看一个关于使用Spring AOP高级特性的例子,来了解它的实现原理。在使用Spring AOP时,对目标对象的增强是通过拦截器来完成的。对于一些应用场合,需要对目标对象本身进行一些处理,比如,如何从一个对象池或对象工厂中获得目标对象等。这样,我们需要使用Spring的TargetSource接口特性,在这里,我们把这类AOP特性当作高级特性的一种,从这些AOP特性的实现原理的了解上,可以看到对AOP基本特性的灵活运用。
在Spring中,提供了许多现成的TargetSource实现,比如下面的HotSwappableTarget-
Source,这个HotSwappableTargetSource使得用户可以以线程安全的方式切换目标对象,提供所谓的热交换功能。这个特性是很有用的,尽管它的开启需要AOP应用进行显式的配置。但这个配置并不复杂,在使用时只需要把这个HotSwappableTargetSource配置到ProxyFactoryBean的target属性就可以了,在需要更换真正的目标对象时,调用HotSwappableTargetSource的swap方法就可以完成。由此可见,对HotSwappableTarget
Source的热交换功能的使用,是需要触发swap方法调用的。这个swap方法的实现很简单,它完成target对象的替换,也就是说,它使用新的target对象来替换原有的target对象。为了保证线程安全,需要把这个替换方法设为synchronized方法,如代码清单3-34所示。


代码清单3-34  HotSwappableTargetSource的swap方法

public synchronized Object swap(Object newTarget) throws IllegalArgumentException {
 Assert.notNull(newTarget, "Target object must not be null");
 Object old = this.target;
 this.target = newTarget;
 return old;
}
public synchronized Object getTarget() {
 return this.target;
}

 

这个target是怎样在AOP中起作用的呢?了解一下对getTarget的调用就很清楚了,这个HotSwappableTargetSource只是对真正的target做了一个简单的封装,以提供热交换的能力,并没有其他特别之处。对getTarget的方法调用关系,如图3-12所示。



 

图3-12  AOP对getTarget的调用


我们以JdkDynamicAopProxy的实现为例子,可以看到在AOP对Proxy代理对象进行invoke方法调用的时候,会使用这个getTarget调用取得真正的目标对象,如果已经调用过swap方法完成目标对象的热交换,那么交给AOP的已经是交换后的目标对象了,如代码清单3-35所示。具体来说,在invoke方法中,我们看到代理对象的取得,是在AopProxy代理对象的拦截器起作用之前,通过targetSource.getTarget()的调用来取得的,而这个代理对象是否被更换过,是由对swap方法的调用来负责的。因而,在invoke方法中,可以看到对于使用了什么样的代理对象,都不会对拦截器的行为做任何的改变。


代码清单3-35  invoke获取目标对象

target = targetSource.getTarget();
if (target != null) {
 targetClass = target.getClass();
}
// Get the interception chain for this method.
// 这里获得定义好的拦截器链。
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice
(method, targetClass);
/**
* Check whether we have any advice. If we don't, we can fallback on direct
* reflective invocation of the target, and avoid creating a MethodInvocation.
*/
// 如果没有设定拦截器,那么就直接调用target的对应方法。
if (chain.isEmpty()) {
 /**
 * We can skip creating a MethodInvocation: just invoke the target directly
 * Note that the final invoker must be an InvokerInterceptor so we know it does
 * nothing but a reflective operation on the target, and no hot swapping or
 * fancy proxying.
 */
 retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
}
else {
 // We need to create a method invocation...
 // 如果有拦截器的设定,那么需要调用拦截器之后才调用目标对象的相应方法。
 //通过构造一个ReflectiveMethodInvocation来实现。
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass,
chain);
 // Proceed to the joinpoint through the interceptor chain.
 retVal = invocation.proceed();
}
// Massage return value if necessary.
if (retVal != null && retVal == target && method.getReturnType().isInstance(proxy) &&
 !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
 /**
 * Special case: it returned "this" and the return type of the method
 * is type-compatible. Note that we can't help if the target sets
 * a reference to itself in another returned object.
 */
 retVal = proxy;
}
return retVal;

 

通过这个getTarget方法,完成了HotSwappableTargetSource与AOP的集成。这个热交换功能为AOP的使用提供了更多便利,对构建应用的基础服务是非常有帮助的,比如可以在运行时支持经常改变的对象的重新配置。对于其他AOP的高级特性,有兴趣的读者可以结合自己的需要进行分析。


3.5  小结
在本章中,我们对最基本Spring AOP的实现方式进行了解析。具体地说,通过使用ProxyFactoryBean/ProxyFactory的实现原理作为例子,对Spring AOP的基本实现和工作原理进行了一些梳理和分析。ProxyFactoryBean是在IoC环境中创建代理的一个很灵活的方法,与其他方法相比,虽然有些繁琐,但却并不妨碍我们从ProxyFactoryBean入手,去了解AOP在Spring中的基本实现。


在Spring AOP的基本实现中,可以了解Spring是如何得到AopProxy代理对象的,以及它是如何利用AopProxy代理对象来对拦截器进行处理的。Proxy代理对象的使用,在Spring AOP的实现过程中是非常重要的一个部分,Spring AOP充分利用了像Java的Proxy、反射技术以及第三方字节码技术实现CGLIB这些技术方案,通过这些技术完成了AOP需要的AopProxy代理对象的生成。回顾整个通过ProxyFactoryBean实现AOP的过程我们可以看到,在它的实现中,首先需要对目标对象进行正确的配置,以及对拦截器的正确配置,以便AopProxy代理对象得以顺利产生;这些配置既可以通过配置ProxyFactoryBean的属性来完成,也可以通过编程式的使用ProxyFactory来实现。这两种AOP的使用方式,只是在表面配置的方式上不同而已,对于内在的AOP实现原理它们是一样的。在生成AopProxy代理对象的时候,Spring AOP设计了专门的AopProxyFactory作为AopProxy代理对象的生产工厂,由它来负责产生相应的AopProxy代理对象,在使用ProxyFactoryBean来得到AopProxy代理对象的时候,它默认地使用的AopProxy代理对象的生产工厂是DefaultAopProxyFactory对象。这个对象是一个在AopProxy生产过程中比较重要的类,它定义了AopProxy代理对象的生成策略,从而决定使用哪一种AopProxy代理对象的生成技术,是使用JDK的Proxy类还是使用CGLIB来完成生产任务。而对于最终的AopProxy代理对象的产生,则是交给JdkDynamicAopProxy和Cglib2AopProxy这两个具体的工厂来完成AopProxy代理对象的生产的,它们使用了不同的生产技术,一种使用的是JDK的Proxy技术,它使用InvocationHandler对象的invoke完成回调,而另一种则是使用CGLIB的技术来生成AopProxy代理对象。


在得到AopProxy代理对象后,在代理的接口方法被调用执行的时候,也就是当AopProxy暴露代理的方法被调用的时候,前面定义的Proxy机制就起作用了。当Proxy对象暴露的方法被调用时,并不是直接地运行目标对象的调用方法,而是会根据Proxy的定义,改变了原有的目标对象方法调用的运行轨迹。这种改变体现在,首先会触发对这些方法调用进行拦截,这些拦截为对目标调用的功能增强提供了工作空间;拦截过程在JDK的Proxy代理对象中,是通过invoke方法来完成的,这个invoke方法是虚拟机触发的一个回调。而在CGLIB的Proxy代理对象中,拦截是由设置好的回调callback方法来完成的。有了这些拦截器的拦截作用,才会有AOP切面增强大显身手的舞台。
在ProxyFactoryBean的回调中,首先会根据配置来对拦截器是否与当前的调用方法相匹配来进行判断。如果当前的调用方法与配置的拦截器相匹配,那么相应的拦截器就会开始发挥作用,这个过程是一个遍历的过程,它会遍历在Proxy代理对象中设置的拦截器链中的所有拦截器。经过这个过程后,在代理对象中定义好的拦截器链里的拦截器会被逐一调用,直到整个拦截器的调用完成为止。在对拦截器的调用完成以后,才是我们最后看到的对目标对象(target)的方法调用。这样,一个普通的Java对象的功能就得到了增强,这种增强和现有的目标对象的设计是正交解耦的,这也是AOP需要达到的一个目标。


在拦截器的调用过程中,实际上已经封装了Spring对AOP的实现,比如对各种通知器的增强织入功能。尽管我们在使用Spring AOP的时候,看到的是一些advice的使用,但实际上这些AOP应用中接触到的这些advice通知是不能直接对目标对象完成增强的,为了完成AOP应用需要的对目标对象的增强,Spring AOP做了许多工作。这些工作包括,对应于每种advice通知,Spring设计了对应的AdviceAdapter通知适配器,正是这些AdviceAdapter通知适配器,实现了advice通知对目标对象的不同的增强方式。对于这些AdviceAdapter通知适配器,在AopProxy代理对象的回调方法中,需要有一个注册机制,它们才能发挥作用。完成这个注册过程之后,实际上在拦截器链中运行的拦截器,已经是经过这些AdviceAdapter适配过的拦截器了。有了这些拦截器,再去结合AopProxy代理对象的拦截回调机制,才能够让advice通知对目标对象的增强作用实实在在地发生。谁知盘中餐,粒粒皆辛苦,在软件开发的世界里,真是没有什么免费午餐。看起来简洁易用的AOP,和IoC容器的实现一样,背后同样蕴含着许多艰苦的努力。


熟悉AOP使用的读者还知道,除了提供AOP的一些基本功能之外,Spring还提供了许多其他高级特性让用户更加方便地使用AOP。对于这些高级特性,在本章中,我们选取了HotSwappableTargetSource来对它的实现原理进行分析,一叶知秋,一管窥豹,希望能够在这里为那些对AOP其他特性的实现感兴趣的读者打开一扇窗。
在本章中,我们提到了Proxy、反射等Java虚拟机特性的使用,CGLIB的使用以及在它们建立的Proxy对象的基础上,对拦截器特性的灵活运用,这些特性都是我们掌握本章内容的背景知识和重要基础。同时,不妨可以反过来看,通过了解本章中AOP的实现原理,也为我们使用这些Java虚拟机的特性以及CGLIB的技术,提供了生动而精彩的应用案例。在AOP的实现中,还有一个值得注意的地方就是,在ProxyFactoryBean得到advisor配置的实现过程中,是通过回调IoC容器的getBean方法来完成的,这个处理既简洁又巧妙,是灵活使用IoC容器功能的一个非常好的实例。以上这些,都是在本章中,除了Spring AOP实现原理本身之外,非常值得我们学习和研究的地方。

  • 大小: 11.6 KB
  • 大小: 51.2 KB
  • 大小: 10.5 KB
评论 共 0 条 请登录后发表评论

发表评论

您还没有登录,请您登录后再发表评论

文章信息

  • hzbook在2010-03-23创建
  • hzbook在2011-05-26更新
  • 标签: spring 3.0
Global site tag (gtag.js) - Google Analytics