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<AbstractClassEnhancePluginDefine> 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • **** AOP 面向切面编程 底层原理 代理!!! 今天AOP课程1、 Spring 传统 AOP2、 Spri...
    luweicheng24阅读 1,396评论 0 1
  • IoC 容器 Bean 的作用域 自定义作用域实现 org.springframework.beans.facto...
    Hsinwong阅读 2,505评论 0 7
  • 亲爱的自己: 你好! 在这个特别的日子里,第一次给你写信,带着一种激动的心情,进行一次心灵的交流,对自己进行一次评...
    漂泊的远行者阅读 302评论 5 3
  • 今天是周五,按以往的习惯今晚可以彻底放松一下,不用写作业了。但今天不行,因为我决定从这个学期改掉以往这种拖...
    ykmm阅读 130评论 0 0
  • (云南•普洱) 传说流淌于人间 天城依然安眠 彩云的南边 有一位不老的神仙 跨越千山万水 寻找你驻留的家园 清新的...
    左岸白水阅读 239评论 0 3