当前版本
skywalking-5.0.0-alpha
已经修复的版本
skywalking-5.0.0-beta
现象
在观察项目中的日志时,发现
有一个拦截全部Controller方法的日志切面WebRequestInterceptor类
//WebRequestInterceptor
@Pointcut("execution(public * com.github.slankka.provider.controller..*.*(..))")
public void webLog() {
}
该方法会拦截skywalking的某个方法。
问题
导致每次Web请求,Controller虽然被执行一次,但是doAround会被执行两次。
如果在doAround中做了某些身份认证,Token校验之类的,则产生了不必要的重复请求。
分析
在doAround中打印:
会发现调用了getSkyWalkingDynamicField。
追查
这个方法由skywalking-agent的InstMethodsInter 生成的:
SkyWalkingAgent通过
List pluginDefines = pluginFinder.find(typeDescription, classLoader);
找到一个具体实现类
ClassEnhancePluginDefine
然后调用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 {
//产生一个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,这里会调用getSkyWalkingDynamicField,但是这里会被WebRequestInterceptor拦截!!!。
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调用getSkyWalkingDynamicField的时候,不小心会被Spring的Aspect拦截。
导致重复调用了一次WebRequestInterceptor的doAround。
解决方法
改写JoinPoint,并排除getSkyWalkingDynamicField方法:
//WebRequestInterceptor
@Pointcut("execution(public * com.github.slankka.provider.controller..*.*(..))
&& !execution(public com.github.slankka.provider.controller..*.getSkyWalkingDynamicField(..))")
public void webLog() {
}
更新
果然一个月之前官方已经修复了这个BUG
修复的原理就是把Spring的org.springframework.aop.support.MethodMatchers 的matches 静态方法给增强了,那就是让Spring忽略EnhancedInstance接口的所有方法!
这方法既粗暴,又优雅,非常值得学习。
相关链接
issue #1114
pull #1118