2018-05-27 Spring拦截器和Skywalking冲突

当前版本

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

你可能感兴趣的:(2018-05-27 Spring拦截器和Skywalking冲突)