如何優(yōu)雅地為Struts2的action加監(jiān)控日志

好久沒寫博客啦,一晃竟已有5個月了,實在是慚愧的很,待整理的checklist還是挺多的,努力一一補上!今天這篇博文源于工作中的一個case:為Struts2中的特定action添加監(jiān)控日志。對Struts2熟悉的童鞋可能會說,這個不就是常規(guī)的aop功能嗎,直接利用其自帶的攔截器(Interceptor)機制就可輕易實現(xiàn),so easy!但最終筆者并沒有這么干,為何呢?后面會說。這期間,筆者也走了好幾條彎路,皆以失敗告終,其中牽涉到aop代理的好一些細節(jié)知識點,以及一些常見的aop誤區(qū),如果沒有這些彎路的嘗試,可能都不會注意到它,故記錄于此,引以為鑒。

問題背景

最近拿到一個需求:對指定的部分請求增加日志監(jiān)控,即在方法調(diào)用前,做一些統(tǒng)一的業(yè)務(wù)邏輯判斷,對于符合條件的則打印方法名、參數(shù)等上下文信息,便于后續(xù)統(tǒng)計分析。由于歷史原因,當前工程較老,其MVC框架還是基于Struts2的!當然,由于忍受不了Struts2的各種安全漏洞、笨重不堪等問題,該工程的MVC框架也正在向spring MVC遷移。目前的情況是,Struts2和spring MVC并存,而此次所要攔截的請求都屬于老的接口,問題就變成如何為Struts2中的action增加日志監(jiān)控。

解決方案

一、初體驗

背景中已提到,項目的MVC框架最終會去掉Struts2并完全切換到spring MVC,因此,為了避免與Struts2過渡耦合,一開始我就避開了其自帶的Interceptor機制,試圖用spring aop來解決它,這樣就跟MVC框架無關(guān)了,后面即便切換到spring MVC,這塊也不用再改動。

首先想到了spring中的自動代理創(chuàng)建器,為了與現(xiàn)有的代碼保持一致,選用了基于Bean名稱匹配的BeanNameAutoProxyCreator,為了講解的方便,筆者寫了個簡單的demo,相關(guān)類定義如下:

/**
 * @author sherlockyb
 * @2017年12月9日
 */
public class HelloAction extends ActionSupport implements ServletRequestAware, ServletResponseAware {
  ......
  public void helloA() {
    System.out.println("say: hello A");
  }
  public void helloB() {
    System.out.println("say: hello B");
  }
  public void helloC() {
    System.out.println("say: hello C");
  }
  ......
}
/**
 * @author sherlockyb
 * @2017年12月10日
 */
public class GreetingMethodInterceptor implements MethodInterceptor {
  private final Logger log = LoggerFactory.getLogger(getClass());
  @Override
  public Object invoke(MethodInvocation invocation) throws Throwable {
    log.info("greeting before invocation...");
    Object result = invocation.proceed();
    log.info("greeting after invocation");
    return result;
  }
}

數(shù)據(jù)庫的聲明式事務(wù)配置appContext-struts2-db.xml如下,之所以要把這個配置專門列出來,因為它與后面的一次報錯息息相關(guān),我們暫且往下走。

<bean id="txAdvice" class="org.sherlockyb.blogdemos.struts2.aop.TransactionManagerAdvice"></bean>
<aop:config>
  <aop:pointcut id="helloPointcut" expression="execution(* org.sherlockyb..*HelloService*.*(..))" />
  <aop:advisor advice-ref="txAdvice" pointcut-ref="helloPointcut" order="1" />
</aop:config>

現(xiàn)在需要對helloAhelloB加日志監(jiān)控,配置如下:

<bean name="greetingInterceptor" class="org.sherlockyb.blogdemos.struts2.aop.GreetingMethodInterceptor" />
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
  <property name="beanNames">
    <list>
      <value>helloAction</value>
    </list>
  </property>
  <property name="interceptorNames">
    <list>
      <value>greetingAdvisor</value>
    </list>
  </property>
</bean>
<bean id="greetingAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
  <property name="advice">
    <ref bean="greetingInterceptor" />
  </property>
  <property name="patterns">
    <list>
      <value>org.sherlockyb.blogdemos.struts2.web.action.HelloAction.helloA</value>
      <value>org.sherlockyb.blogdemos.struts2.web.action.HelloAction.helloB</value>
    </list>
  </property>
</bean>

然后用postman測試action請求http://localhost/hello/helloA.action,直接報錯:

java.lang.NoSuchMethodException: com.sun.proxy.$Proxy39.helloA()
    at ognl.OgnlRuntime.callAppropriateMethod(OgnlRuntime.java:1247)
    at ognl.ObjectMethodAccessor.callMethod(ObjectMethodAccessor.java:68)
    at com.opensymphony.xwork2.ognl.accessor.XWorkMethodAccessor.callMethodWithDebugInfo(XWorkMethodAccessor.java:117)
    at com.opensymphony.xwork2.ognl.accessor.XWorkMethodAccessor.callMethod(XWorkMethodAccessor.java:108)
    at ognl.OgnlRuntime.callMethod(OgnlRuntime.java:1370)
    at ognl.ASTMethod.getValueBody(ASTMethod.java:91)
    at ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:212)
    at ognl.SimpleNode.getValue(SimpleNode.java:258)
    at ognl.Ognl.getValue(Ognl.java:467)
    at ognl.Ognl.getValue(Ognl.java:431)
    at com.opensymphony.xwork2.ognl.OgnlUtil$3.execute(OgnlUtil.java:352)
    at com.opensymphony.xwork2.ognl.OgnlUtil.compileAndExecuteMethod(OgnlUtil.java:404)
    at com.opensymphony.xwork2.ognl.OgnlUtil.callMethod(OgnlUtil.java:350)
    at com.opensymphony.xwork2.DefaultActionInvocation.invokeAction(DefaultActionInvocation.java:430)
    at com.opensymphony.xwork2.DefaultActionInvocation.invokeActionOnly(DefaultActionInvocation.java:290)
    at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:251)
  ...

NoSuchMethodException?奇了怪了,TestAction中明明有helloA方法,并且patterns配置中也加了org.sherlockyb.blogdemos.struts2.web.action.helloA的配置,為啥最終生成的代理類卻沒有這個方法呢?到底是哪里出了問題?帶著這個疑問,我們直接從異常信息著手:既然它報的是$Proxy39這個類沒有helloA方法,那我們就來debug看一下$Proxy39究竟有哪些內(nèi)容。

因為OgnlRuntime粒度太細了,太多地方調(diào)用,若在這里面打斷點還得根據(jù)條件斷點才能定位到TestAction的調(diào)用,比較麻煩,故筆者選擇了在調(diào)用棧中所處位置較為上層的DefaultActionInvocation。定位到異常信息DefaultActionInvocation.invokeAction(DefaultActionInvocation.java:430)對應(yīng)的源碼,斷點打在了源碼的第430行,如下:

debug1.png

然后debug模式運行應(yīng)用,截獲的debug信息如下:

debug2.png

從1處可以看出,原來$Proxy39是JDK動態(tài)代理生成的代理類,至于為啥是JDK代理,可以注意到變量proxyTargetClass默認是false的,也就是說spring aop 默認采用JDK動態(tài)代理。我們知道,JDK動態(tài)代理是面向接口的,只會為目標類所實現(xiàn)的接口生成代理方法,查看2處interface的內(nèi)容如下:

[interface org.apache.struts2.interceptor.ServletRequestAware, interface org.apache.struts2.interceptor.ServletResponseAware, interface com.opensymphony.xwork2.Action, interface com.opensymphony.xwork2.Validateable, interface com.opensymphony.xwork2.ValidationAware, interface com.opensymphony.xwork2.TextProvider, interface com.opensymphony.xwork2.LocaleProvider, interface java.io.Serializable]

這些不正是TestAction直接(ServletRequestAware等)或間接(Action等)實現(xiàn)的接口嘛,而helloAhelloBTestAction自定義的方法,并不在這些接口的方法中,那么最終的代理類$Proxy39自然不會含有這倆方法,調(diào)用時就會報上述錯誤。

二、改進

我們的目的是為TestAction中的helloAhelloB方法進行動態(tài)代理,但它們不屬于TestAction所實現(xiàn)接口中的任何一個方法,顯然JDK動態(tài)代理滿足不了需求,轉(zhuǎn)向CGLib代理,于是將proxyTargetClass參數(shù)改為true,強制其走CGLib代理。配置如下:

<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
  ......
  <property name="proxyTargetClass">
    <value>true</value>
  </property>
  ......
</bean>
……

依舊用postman測試,依舊報錯了:

[ERROR] 2017-12-12 23:17:49,450 [resin-port-80-48] struts2.dispatcher.DefaultDispatcherErrorHandler (CommonsLogger.java:42) -Exception occurred during processing request: Unable to instantiate Action, helloAction,  defined for 'helloA' in namespace '/hello'Error creating bean with name 'helloAction' defined in class path resource [appContext-struts2-action.xml]: Initialization of bean failed; nested exception is org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class [class com.sun.proxy.$Proxy40]: Common causes of this problem include using a final class or a non-visible class; nested exception is java.lang.IllegalArgumentException: Cannot subclass final class class com.sun.proxy.$Proxy40
Unable to instantiate Action, helloAction,  defined for 'helloA' in namespace '/hello'Error creating bean with name 'helloAction' defined in class path resource [appContext-struts2-action.xml]: Initialization of bean failed; nested exception is org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class [class com.sun.proxy.$Proxy40]: Common causes of this problem include using a final class or a non-visible class; nested exception is java.lang.IllegalArgumentException: Cannot subclass final class class com.sun.proxy.$Proxy40 - action - file:/D:/DevCode/workspace/blog-demos/struts2/target/classes/org/sherlockyb/blogdemos/struts2/web/action/conf/struts-hello.xml:9:61
    at com.opensymphony.xwork2.DefaultActionInvocation.createAction(DefaultActionInvocation.java:317)
    at com.opensymphony.xwork2.DefaultActionInvocation.init(DefaultActionInvocation.java:398)
    at com.opensymphony.xwork2.DefaultActionProxy.prepare(DefaultActionProxy.java:194)
    at org.apache.struts2.impl.StrutsActionProxy.prepare(StrutsActionProxy.java:63)
    at org.apache.struts2.impl.StrutsActionProxyFactory.createActionProxy(StrutsActionProxyFactory.java:37)
    at com.opensymphony.xwork2.DefaultActionProxyFactory.createActionProxy(DefaultActionProxyFactory.java:58)
    at org.apache.struts2.dispatcher.Dispatcher.serviceAction(Dispatcher.java:565)
    at org.apache.struts2.dispatcher.ng.ExecuteOperations.executeAction(ExecuteOperations.java:81)
    at org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter.doFilter(StrutsPrepareAndExecuteFilter.java:99)

我們可以注意到異常棧中最底層的一條錯誤信息:Cannot subclass final class class com.sun.proxy.$Proxy40,這條錯誤是導(dǎo)致上述報錯的最根本原因(root cause),其對應(yīng)的調(diào)用鏈詳情如下:

Caused by: java.lang.IllegalArgumentException: Cannot subclass final class class com.sun.proxy.$Proxy40
    at net.sf.cglib.proxy.Enhancer.generateClass(Enhancer.java:446)
    at net.sf.cglib.transform.TransformingClassGenerator.generateClass(TransformingClassGenerator.java:33)
    at net.sf.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25)
    at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:216)
    at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:377)
    at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:285)
    at org.springframework.aop.framework.Cglib2AopProxy.getProxy(Cglib2AopProxy.java:201)

也就是說,當前面配置的BeanNameAutoProxyCreator嘗試為目標類com.sun.proxy.$Proxy40生成CGLib代理時,卻發(fā)現(xiàn)這貨是final的!也就是說JDK動態(tài)代理生成的代理類是final的,你們知道這個知識點嘛?反正在此之前我是沒留意過這個,知道的童鞋可舉個爪,那說明你走的比我遠,要繼續(xù)保持這樣的好奇心。我們言歸正傳,上述錯誤表明,在BeanNameAutoProxyCreator生效前,已經(jīng)有第三者TestAction以JDK動態(tài)代理的方式生成了代理類,導(dǎo)致無法再進行CGLib代理。這個第三者到底是誰呢?

起初我想到了Struts2的Interceptor機制,會不會是Struts2事先采用JDK動態(tài)代理的方式為TestAction生成了代理,以便加上各種Interceptor增強邏輯?很快,我通過debug跟蹤Struts2源碼否決了這個猜測:

1、action是交給spring管理的,即StrutsSpringObjectFactory,我們知道action的作用域是prototype的,即每來一個請求,Struts2都會通過DefaultActionFactory來buildAction,而實際的創(chuàng)建則是委托給StrutsSpringObjectFactory來處理,也就說Struts2是拿到spring容器構(gòu)建好的action之后,才做后續(xù)的Interceptor過程;

2、通過仔細閱讀DefaultActionInvocation的invoke源碼可知,Struts2的Interceptor機制既不是通過JDK動態(tài)代理來實現(xiàn),也沒有采納CGLib代理,而是巧用責(zé)任鏈和迭代等代碼技巧來實現(xiàn)的,具體細節(jié)等后面單獨一篇博文細說。

那到底是何方神圣偷偷做了這個事兒呢?謎底盡在源碼中!通過源碼來跟蹤下action的創(chuàng)建過程:

1、DefaultActionInvocation——action的創(chuàng)建(每次請求必走邏輯)

protected void createAction(Map<String, Object> contextMap) {
  // load action
  String timerKey = "actionCreate: " + proxy.getActionName();
  try {
    UtilTimerStack.push(timerKey);
    action = objectFactory.buildAction(proxy.getActionName(), proxy.getNamespace(), proxy.getConfig(), contextMap);
  } catch (InstantiationException e) {
    throw new XWorkException("Unable to intantiate Action!", e, proxy.getConfig());
  }
  ......
}

2、StrutsSpringObjectFactory——spring容器層面的,bean的創(chuàng)建

@Override
public Object buildBean(String beanName, Map<String, Object> extraContext, boolean injectInternal) throws Exception {
  Object o;
  if (appContext.containsBean(beanName)) {
    o = appContext.getBean(beanName);   //action從spring容器中獲取
  } else {
    Class beanClazz = getClassInstance(beanName);
    o = buildBean(beanClazz, extraContext);
  }
  if (injectInternal) {
    injectInternalBeans(o);
  }
  return o;
}

3、AbstractAutowireCapableBeanFactory——spring容器中,bean的初始化以及之后的postProcess過程

protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
  ......
  if (mbd == null || !mbd.isSynthetic()) {
    wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
  }
  try {
    invokeInitMethods(beanName, wrappedBean, mbd);
  }
  catch (Throwable ex) {
    throw new BeanCreationException((mbd != null ? mbd.getResourceDescription() : null),
                    beanName, "Invocation of init method failed", ex);
  }
  //Bean初始化之后,postProcess處理,如一系列的AutoProxyCreator
  if (mbd == null || !mbd.isSynthetic()) { 
    wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
  }
  return wrappedBean;
}

最終定位到AspectJAwareAdvisorAutoProxyCreator,直接看debug調(diào)用棧:![1]

debug3.png

首先,我們先看下wrapIfNecessary的核心代碼片段如下,其大致功能就是為目標bean創(chuàng)建代理類:先看下bean有沒有相關(guān)的advice,如果有,則通過createProxy為其創(chuàng)建代理類;否則直接返回原始bean!

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
        ......
        // Create proxy if we have advice.
        Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
        if (specificInterceptors != DO_NOT_PROXY) {
            this.advisedBeans.add(cacheKey);
            Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
            this.proxyTypes.put(cacheKey, proxy.getClass());
            return proxy;
        }
        this.nonAdvisedBeans.add(cacheKey);
        return bean;
    }

這里bean的debug信息如下:
debug4.png

HelloAction@3d696299,這正是我們在xml中定義的原始bean實例!也就說,AspectJAwareAdvisorAutoProxyCreator就是傳說中的第三者。那么問題來了:AspectJAwareAdvisorAutoProxyCreator是在什么情況下又是何時被創(chuàng)建的呢?我們并沒有顯式地在哪里指定,要讓它為HelloAction創(chuàng)建代理,這二者是如何關(guān)聯(lián)的起來的呢?

在eclipse中,定位到AspectJAwareAdvisorAutoProxyCreator類的源碼,選中其類名,直接Ctrl+Shift+G查看其在workspace中的所有引用(reference)如下:

debug5.png

進一步跟進registerAspectJAutoProxyCreatorIfNecessary方法,直接Ctrl+Shift+H查看該方法的上層調(diào)用鏈:

debug6.png

到這里第一個問題就比較清晰了:由于appContext-struts2-db.xml中通過<aop:config>為數(shù)據(jù)庫操作配置了聲明式事務(wù),導(dǎo)致AspectJAwareAdvisorAutoProxyCreator實例的構(gòu)建。我們再來看第二個問題,即這個AutoProxyCreator是如何與HelloAction關(guān)聯(lián)的,回顧下前面的wrapIfNecessary的源碼片段,其中有一個getAdvicesAndAdvisorsForBean方法,它是定義在抽象類AbstractAutoProxyCreator中的抽象方法,其功能如下方的官方注釋所說:判斷當前目標bean是否需要代理,如果是則返回對應(yīng)的增強(advice)或切面(advisor)集。具體實現(xiàn)則交給各具體的子類,典型的模板方法設(shè)計。

/**
  * Return whether the given bean is to be proxied, what additional
  * advices (e.g. AOP Alliance interceptors) and advisors to apply.
  */
  protected abstract Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource customTargetSource) throws BeansException;

AbstractAutoProxyCreator類的繼承結(jié)構(gòu)如下:

debug7.png

其中的AbstractAdvisorAutoProxyCreator很關(guān)鍵,它是第三者AspectJAwareAdvisorAutoProxyCreator的直接父類,并實現(xiàn)抽象方法getAdvicesAndAdvisorsForBean,邏輯如下:

@Override
protected Object[] getAdvicesAndAdvisorsForBean(Class beanClass, String beanName, TargetSource targetSource) {
    List advisors = findEligibleAdvisors(beanClass, beanName);  //找出bean相關(guān)的advisors
    if (advisors.isEmpty()) {
      return DO_NOT_PROXY;  //如果沒有advisor,則直接返回約定的DO_NOT_PROXY,表示無需代理
    }
    return advisors.toArray();
}

再看下findEligibleAdvisors具體做了什么:

protected List<Advisor> findEligibleAdvisors(Class beanClass, String beanName) {
  //獲取當前spring容器中所有的Advisor,除了FactoryBean類型的和目前已構(gòu)建過的
  List<Advisor> candidateAdvisors = findCandidateAdvisors();
  List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);    //從中篩選出可以應(yīng)用在bean上的advisor
  extendAdvisors(eligibleAdvisors);
  if (!eligibleAdvisors.isEmpty()) {
    eligibleAdvisors = sortAdvisors(eligibleAdvisors);
  }
  return eligibleAdvisors;
}

最終通過層層代碼跳轉(zhuǎn),我們來到了AopUtils中判定advisor與bean是否匹配的關(guān)鍵邏輯:

public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
  if (!pc.getClassFilter().matches(targetClass)) {  //先看類級別是否匹配,不匹配就直接返回false
    return false;
  }
  //方法匹配器:切點的一部分,判定目標方法是否與切點表達式匹配
  MethodMatcher methodMatcher = pc.getMethodMatcher();
  IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
  if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
    introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
  }
  Set<Class> classes = new HashSet<Class>(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
  classes.add(targetClass);
  /**這里的classes由兩部分組成:一個是目標類所實現(xiàn)的所有接口;一個是目標類本身(targetClass)。結(jié)合下面的循環(huán)掃描Methods的邏輯,也就是說,它會掃描目標類所實現(xiàn)的所有接口中定義的方法和目標類自身定義的方法
  */
  for (Class<?> clazz : classes) {
    Method[] methods = clazz.getMethods();
    for (Method method : methods) {
      if ((introductionAwareMethodMatcher != null && 
           introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions)) ||
           methodMatcher.matches(method, targetClass)) {
        return true;
      }
    }
  }
  return false;
}

看到這兒整個流程就清晰了:由于我們配置了greetingAdvisor,并且patternsHelloAction中的helloAhelloB匹配,導(dǎo)致相應(yīng)的advisor與目標bean(HelloAction)關(guān)聯(lián)了,即getAdvicesAndAdvisorsForBean返回的Interceptors不為DO_NOT_PROXY,于是走了下面的createProxy邏輯,又因為AspectJAwareAdvisorAutoProxyCreator的配置項proxyTargetClass默認是false的,進而為HelloAction創(chuàng)建了JDK動態(tài)代理。

三、最終版

經(jīng)過上述兩次錯誤分析,我們得知以下幾點:

1、首先使用CGLib的方式為HelloAction創(chuàng)建代理是必須的,因為我們所要代理的方法是HelloAction自定義的,且不在其所實現(xiàn)接口的方法列表中,面向接口的JDK動態(tài)代理行不通;

2、只要當前應(yīng)用中別的地方事先配置了<aop:config>(比如最常用的聲明式事務(wù)),就無法使用BeanNameAutoProxyCreator的方式為HelloAction創(chuàng)建CGLib代理!因為要為目標類的部分方法生成代理,其配置項interceptorNames就只能用Advisor而非普通的bean名稱,而Advisor又會被AspectJAwareAdvisorAutoProxyCreator掃描到,最終導(dǎo)致上述二次代理的問題。

最終去掉了BeanNameAutoProxyCreatorgreetingAdvisor,改為<aop:config>通過指定proxy-target-class為true強制AspectJAwareAdvisorAutoProxyCreator走CGLib代理,配置如下:

<aop:config proxy-target-class="true"> <aop:pointcut id="pt-greet" expression="( execution(* org.sherlockyb.blogdemos.struts2.web.action.HelloAction.helloA(..)) or execution(* org.sherlockyb.blogdemos.struts2.web.action.HelloAction.helloB(..)) )"/>
  <aop:advisor id="ad-greet" advice-ref="greetingInterceptor" pointcut-ref="pt-greet"/> 
</aop:config>

最后的攔截效果如下:

[INFO] 2017-12-14 23:44:03,972 [resin-port-80-51] struts2.aop.GreetingMethodInterceptor (GreetingMethodInterceptor.java:33) -greeting before invocation...
say: hello A
[INFO] 2017-12-14 23:44:08,234 [resin-port-80-51] struts2.aop.GreetingMethodInterceptor (GreetingMethodInterceptor.java:35) -greeting after invocation

總結(jié)

一、JDK與CGLib動態(tài)代理的本質(zhì)區(qū)別

1.1 JDK動態(tài)代理

JDK動態(tài)代理是面向接口的,即被代理的目標類必須實現(xiàn)接口,且最終只會為目標類所實現(xiàn)的所有接口中的方法生成代理方法,對于目標類中包含的但是非接口中的方法,是不會生成對應(yīng)的代理方法,methodA和methodB就是例子,這是由JDK代理的實現(xiàn)機制所決定了的:通過繼承自Proxy類,實現(xiàn)目標類所實現(xiàn)的接口來生成代理類。

JDK動態(tài)代理生成的代理類,以$Proxy開頭,后面的計數(shù)數(shù)字表示當前生成的是第幾個代理類。且代理類是final的,不可被繼承。

1.2 CGLib動態(tài)代理

而CGLib則是通過繼承目標類,得到其子類的方式生成代理,而final類是不能被繼承的,因為CGLib無法為final類生成代理。

CGLib代理生成的代理類含有$$,比如HelloAction$$EnhancerByCGLIB$$ff7d443b。

二、對aop的不熟練所引發(fā)的問題

對aop的不熟練,使得我們在用的時候,往往就容易忽視了一些細節(jié),如當前采用的動態(tài)代理是JDK的還是CGLib的,默認選擇是什么?都有哪些配置項,配置項的默認值,以及各配置項對最終生成代理結(jié)果的影響如何?當前類是否被多次代理?當出現(xiàn)了多次代理,代理的順序又是如何?

對于第二次報錯,其本質(zhì)問題是屬于二次代理的問題。有網(wǎng)友也遇到過類似的問題——記一次Spring的aop代理Mybatis的DAO所遇到的問題,只不過是在MyBatis上踩的坑,后續(xù)將會針對spring aop單獨另開博文詳解,盡情期待~

三、Struts2的Interceptor機制原理

Struts2的Interceptor機制是屬于aop功能,按理說用常規(guī)的動態(tài)代理就可實現(xiàn)。但是由初體驗 小節(jié)中debug過程可知,它并沒有基于常規(guī)的動態(tài)字節(jié)碼技術(shù)如JDK動態(tài)代理、CGLib動態(tài)代理等,而是通過責(zé)任鏈模式和迭代的巧妙結(jié)合,實現(xiàn)了aop的功能,有興趣的話也可研究一下。

同步更新到此處

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容