當前版本
skywalking-5.0.0-alpha
已經(jīng)修復的版本
skywalking-5.0.0-beta
現(xiàn)象
在觀察項目中的日志時床三,發(fā)現(xiàn)
有一個攔截全部Controller方法的日志切面WebRequestInterceptor類
//WebRequestInterceptor
@Pointcut("execution(public * com.github.slankka.provider.controller..*.*(..))")
public void webLog() {
}
該方法會攔截skywalking的某個方法惨险。
問題
導致每次Web請求缕棵,Controller雖然被執(zhí)行一次,但是doAround會被執(zhí)行兩次总寒。
如果在doAround中做了某些身份認證糕簿,Token校驗之類的怀挠,則產(chǎn)生了不必要的重復請求。
分析
在doAround中打恿秘啊:
會發(fā)現(xiàn)調(diào)用了getSkyWalkingDynamicField鹰晨。
追查
這個方法由skywalking-agent的InstMethodsInter 生成的:
SkyWalkingAgent通過
List<AbstractClassEnhancePluginDefine> pluginDefines = pluginFinder.find(typeDescription, classLoader);
找到一個具體實現(xiàn)類
ClassEnhancePluginDefine
然后調(diào)用define
DynamicType.Builder<?> possibleNewBuilder =
define.define(typeDescription.getTypeName(), newBuilder, classLoader, context);
然后生成了InstMethodsInter
這個類intercept的方法中:
@RuntimeType
public Object intercept(@This Object obj,
@AllArguments Object[] allArguments,
@SuperCall Callable<?> zuper,
@Origin Method method
) throws Throwable {
//產(chǎn)生一個EnhancedInstance
EnhancedInstance targetObject = (EnhancedInstance)obj;
MethodInterceptResult result = new MethodInterceptResult();
try {
interceptor.beforeMethod(targetObject, method, allArguments, method.getParameterTypes(),
result);
} catch (Throwable t) {
logger.error(t, "class[{}] before method[{}] intercept failure", obj.getClass(), method.getName());
}
Object ret = null;
try {
if (!result.isContinue()) {
ret = result._ret();
} else {
ret = zuper.call();
}
} catch (Throwable t) {
try {
interceptor.handleMethodException(targetObject, method, allArguments, method.getParameterTypes(),
t);
} catch (Throwable t2) {
logger.error(t2, "class[{}] handle method[{}] exception failure", obj.getClass(), method.getName());
}
throw t;
} finally {
try {
ret = interceptor.afterMethod(targetObject, method, allArguments, method.getParameterTypes(),
ret);
} catch (Throwable t) {
logger.error(t, "class[{}] after method[{}] intercept failure", obj.getClass(), method.getName());
}
}
return ret;
}
interceptor.afterMethod對應
GetBeanInterceptor 的 afterMethod,這里會調(diào)用getSkyWalkingDynamicField止毕,但是這里會被WebRequestInterceptor攔截D@!扁凛!忍疾。
public class GetBeanInterceptor implements InstanceMethodsAroundInterceptor {
@Override
public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
MethodInterceptResult result) throws Throwable {
}
@Override
public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
Object ret) throws Throwable {
if (ret instanceof EnhancedInstance) {
((EnhanceRequireObjectCache)((EnhancedInstance)ret).getSkyWalkingDynamicField()).setNativeWebRequest((NativeWebRequest)objInst.getSkyWalkingDynamicField());
}
return ret;
}
@Override
public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
Class<?>[] argumentsTypes, Throwable t) {
}
}
結論
getSkyWalkingDynamicField出自EnhancedInstance
在InstMethodsInter.afterMethod調(diào)用getSkyWalkingDynamicField的時候,不小心會被Spring的Aspect攔截谨朝。
導致重復調(diào)用了一次WebRequestInterceptor的doAround卤妒。
解決方法
改寫JoinPoint,并排除getSkyWalkingDynamicField方法:
//WebRequestInterceptor
@Pointcut("execution(public * com.github.slankka.provider.controller..*.*(..))
&& !execution(public com.github.slankka.provider.controller..*.getSkyWalkingDynamicField(..))")
public void webLog() {
}
更新
果然一個月之前官方已經(jīng)修復了這個BUG
修復的原理就是把Spring的org.springframework.aop.support.MethodMatchers 的matches 靜態(tài)方法給增強了字币,那就是讓Spring忽略EnhancedInstance接口的所有方法则披!
這方法既粗暴,又優(yōu)雅洗出,非常值得學習士复。
相關鏈接
issue #1114
pull #1118