好久沒寫博客啦,一晃竟已有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)在需要對helloA和helloB加日志監(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行,如下:

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

從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)的接口嘛,而helloA和helloB是TestAction自定義的方法,并不在這些接口的方法中,那么最終的代理類$Proxy39自然不會含有這倆方法,調(diào)用時就會報上述錯誤。
二、改進
我們的目的是為TestAction中的helloA和helloB方法進行動態(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]

首先,我們先看下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信息如下:
HelloAction@3d696299,這正是我們在xml中定義的原始bean實例!也就說,AspectJAwareAdvisorAutoProxyCreator就是傳說中的第三者。那么問題來了:AspectJAwareAdvisorAutoProxyCreator是在什么情況下又是何時被創(chuàng)建的呢?我們并沒有顯式地在哪里指定,要讓它為HelloAction創(chuàng)建代理,這二者是如何關(guān)聯(lián)的起來的呢?
在eclipse中,定位到AspectJAwareAdvisorAutoProxyCreator類的源碼,選中其類名,直接Ctrl+Shift+G查看其在workspace中的所有引用(reference)如下:

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

到這里第一個問題就比較清晰了:由于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)如下:

其中的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,并且patterns與HelloAction中的helloA和helloB匹配,導(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)致上述二次代理的問題。
最終去掉了BeanNameAutoProxyCreator和greetingAdvisor,改為<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的功能,有興趣的話也可研究一下。
同步更新到此處