SkyWalking 源码解析 —— Agent 插件(三)之 SpringMVC

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】

摘要: 原创出处 http://www.iocoder.cn/SkyWalking/agent-plugin-spring-mvc/ 「芋道源码」欢迎转载,保留摘要,谢谢!

本文主要基于 SkyWalking 3.2.6 正式版


1. 概述

2. core-patch

core-patch 模块,给 Spring 打补丁,解决因为 Agent 对类的增强操作导致的冲突。

打脸提示:笔者对 Spring 的一些( 大部分 )机制了解的较浅薄,所以本小节更多的是粘贴代码 + 相关 Issue 。

2.1 AopProxyFactoryInstrumentation

原因和目的,参见 Issue#581

SkyWalking Agent 在增强类的构造方法或者实例方法时,会自动实现 EnhancedInstance 接口,导致 Spring 的 DefaultAopProxyFactory#hasNoUserSuppliedProxyInterfaces(AdvisedSupport) 返回 false 错误,实际应该返回 true

// DefaultAopProxyFactory.java
/**
 * Determine whether the supplied {@link AdvisedSupport} has only the
 * {@link org.springframework.aop.SpringProxy} interface specified
 * (or no proxy interfaces specified at all).
 */
private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
    Class<?>[] ifcs = config.getProxiedInterfaces();
    return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0])));
}

org.skywalking.apm.plugin.tomcat78x.define.TomcatInstrumentation ,实现 ClassInstanceMethodsEnhancePluginDefine 抽象类,定义了方法切面,代码如下:


org.skywalking.apm.plugin.spring.patch.CreateAopProxyInterceptor ,实现 InstanceMethodsAroundInterceptor 接口,ClassInstanceMethodsEnhancePluginDefine 的拦截器。代码如下:

  • #afterMethod(...) 方法,代码如下:
    • 第 47 行:若目标类实现了 EnhancedInstance 接口,返回 true
    • 第 50 行:否则,返回原有结果 ret

2.2 AutowiredAnnotationProcessorInstrumentation

原因和目的,参见 Issue#622Issue#624

Spring 的 AutowiredAnnotationBeanPostProcessor#determineCandidateConstructors(Class, String) 方法,返回有三种情况:

  1. 带有 @Autowired 参数的构造方法
  2. 仅有一个带参数的构造方法
  3. 不带参数的构造方法

因为 SkyWalking 增强机制会生成一个私有构造方法,导致所有被增强的类原先满足第二种情况的,Spring 选择了第三种情况,导致报构造方法不存在。

通过 AutowiredAnnotationProcessorInterceptor ,会过滤掉私有构造方法,从而解决冲突问题。


org.skywalking.apm.plugin.spring.patch.define.AutowiredAnnotationProcessorInstrumentation ,实现 ClassInstanceMethodsEnhancePluginDefine 抽象类,定义了方法切面,代码如下:


org.skywalking.apm.plugin.spring.patch.CreateAopProxyInterceptor ,实现 InstanceMethodsAroundInterceptor 和 InstanceConstructorInterceptor接口,AutowiredAnnotationProcessorInstrumentation 的拦截器。代码如下:

  • #onConstruct(...) 方法,创建类与构造方法的映射。代码如下:
    • 第 115 行:创建类与构造方法的映射 candidateConstructorsCache用于缓存
    • 第 117 行:设置到私有变量( SkyWalking 增强生成 )。
  • #afterMethod(...) 方法,处理自动实现 EnhancedInstance 接口的类,和 Spring 的 AutowiredAnnotationBeanPostProcessor#determineCandidateConstructors(Class, String) 的冲突。代码如下:
    • 第 54 行:若 beanClass 实现了 EnhancedInstance 接口。
    • 第 56 至 57 行:从 candidateConstructorsCache 缓存中获得构造方法。
    • 第 58 行:缓存中不存在对应的构造方法,遍历 beanClass 的类的构造方法,缓存并返回。
      • ----- ret == null 原本方法没找到构造方法,存在冲突 -----
      • 第 80 至 86 行:获得构造方法集合,排除私有构造方法。为什么排除私有构造方法?因为 SkyWalking 与 Spring 的冲突,就是因为 SkyWalking 自动生成的私有构造方法,所以需要排除。
      • 第 89 至 90 行:【冲突点】让第二种情况,依然走第二种
      • 第 91 至 94 行:选择第一个构造方法。
      • ----- ret != null 原本方法就找到构造方法,不存在冲突 -----
      • 第 97 行:使用原本方法就找到构造方法
      • ----- all -----
      • 第 100 行:缓存构造方法到 candidateConstructorsCache 中。
    • 第 103 行:返回结果。

ps:这块略复杂,如果笔者未解释清晰,那是因为我菜。

3. mvc-annotation-commons

mvc-annotation-commons 模块,提供公用代码,提供给 mvc-annotation-4.x-pluginmvc-annotation-3.x-plugin 使用。

3.1 PathMappingCache

org.skywalking.apm.plugin.spring.mvc.commons.PathMappingCache ,缓存 Controller 的所有请求路径,一个 Controller 对象一个 PathMappingCache 对象。代码如下:

3.2 EnhanceRequireObjectCache

org.skywalking.apm.plugin.spring.mvc.commons.EnhanceRequireObjectCache ,在 PathMappingCache 的基础上,增加 nativeWebRequest 属性。实际上,一个 Controller 对象一个 EnhanceRequireObjectCache 对象。代码如下:

  • pathMappingCache 属性,「 3.2 PathMappingCache 」 对象。
  • nativeWebRequest 属性,当前 Request 对象。因为一个 Controller 对应一个EnhanceRequireObjectCache 对象,nativeWebRequest 对象被多线程共享时会冲突,在 SkyWalking 5.x 版本会修改成 ThreadLocal 属性,解决并发问题

3.3 Constants

org.skywalking.apm.plugin.spring.mvc.commons.Constants ,枚举 org.skywalking.apm.plugin.spring.mvc.commons.interceptor 包下的拦截器类名。

3.4 拦截器

org.skywalking.apm.plugin.spring.mvc.commons.interceptor 包下共有四种拦截器,如下图:

结合 「 4. mvc-annotation-4.x-plugin 」 ,我们一起分享。

4. mvc-annotation-4.x-plugin

本小节涉及到的类如下图:

我们整理如下:

Instrumentation Interceptor
AbstractControllerInstrumentation ControllerConstructorInterceptor
InvocableHandlerInstrumentation InvokeForRequestInterceptor
HandlerMethodInstrumentation GetBeanInterceptor
ControllerInstrumentation RequestMappingMethodInterceptor
RestControllerInstrumentation RestMappingMethodInterceptor

4.1 AbstractSpring4Instrumentation

org.skywalking.apm.plugin.spring.mvc.v4.define.AbstractSpring4Instrumentation ,实现 ClassInstanceMethodsEnhancePluginDefine 抽象类,所有 Spring MVC 4.x 的 Instrumentation 的抽象基类。通过定义 #witnessClasses() 方法,声明 Spring MVC 4.x 的插件生效,需要项目里包括 org.springframework.web.servlet.tags.ArgumentTag 类。

通过这样的方式,区分 Spring MVC 4.x 和 3.x 的插件。ArgumentTag 在 Spring MVC 3.x 是不存在的。

#witnessClasses() 的相关方法,在 《SkyWalking 源码分析 —— Agent 插件体系》 有详细解析。

4.2 AbstractControllerInstrumentation

org.skywalking.apm.plugin.spring.mvc.v4.define.AbstractControllerInstrumentation ,实现 AbstractSpring4Instrumentation 抽象类,定义了方法切面,代码如下:

分成两部分:

AbstractControllerInstrumentation 是一个抽象基类,有 「 4.5 ControllerInstrumentation 」「 4.6 RestControllerInstrumentation 」 两个子类,实现 #getEnhanceAnnotations() 抽象方法,返回不同的类注解,从而拦截不同的类。

4.2.1 ControllerConstructorInterceptor

org.skywalking.apm.plugin.spring.mvc.v4.ControllerConstructorInterceptor ,实现 InstanceConstructorInterceptor 接口,AbstractControllerInstrumentation 的拦截器。代码如下:

  • #onConstruct(...) 方法,代码如下:
    • 第 45 至 53 行:解析类的请求路径。
    • 第 55 至 56 行:创建 EnhanceRequireObjectCache 缓存对象。
    • 第 59 行:调用 EnhancedInstance#setSkyWalkingDynamicField(value) 方法,设置到 Controller 的私有变量( SkyWalking 自动生成 )。即,Controller : EnhanceRequireObjectCache = 1 : 1

4.3 InvocableHandlerInstrumentation

org.skywalking.apm.plugin.spring.mvc.v4.define.InvocableHandlerInstrumentation ,实现 AbstractSpring4Instrumentation 抽象类,定义了方法切面,代码如下:

4.3.1 InvokeForRequestInterceptor

org.skywalking.apm.plugin.spring.mvc.commons.interceptor.InvokeForRequestInterceptor ,实现 InstanceMethodsAroundInterceptor 接口,InvocableHandlerInstrumentation 的拦截器。代码如下:

  • #beforeMethod(...) 方法,代码如下:
    • 第 42 行:调用 EnhancedInstance#setSkyWalkingDynamicField(value) 方法,设置 NativeWebRequest 到 ServletInvocableHandlerMethod 的私有变量( SkyWalking 自动生成 )。
      • objInst 类型为 ServletInvocableHandlerMethod 类( 继承 InvocableHandlerMethod 类 ),每次请求都会创建新的该对象,因此设置 NativeWebRequest 对象,线程安全。
      • allArguments[0] 类型为 NativeWebRequest 类。

4.4 HandlerMethodInstrumentation

org.skywalking.apm.plugin.spring.mvc.v4.define.HandlerMethodInstrumentation ,实现 AbstractSpring4Instrumentation 抽象类,定义了方法切面,代码如下:

  • 拦截 HandlerMethod#getBean() 方法,提交给 GetBeanInterceptor 处理。
  • 注意,上面我们看到的 ServletInvocableHandlerMethod 继承的 InvocableHandlerMethod 类,继承了 HandlerMethod.java 类。(绕口令)。

4.4.1 GetBeanInterceptor

org.skywalking.apm.plugin.spring.mvc.commons.interceptor.GetBeanInterceptor ,实现 InstanceMethodsAroundInterceptor 接口,InvocableHandlerInstrumentation 的拦截器。代码如下:

  • #afterMethod(...) 方法,代码如下:
    • 第 44 至 48 行: 调用 EnhancedInstance#setSkyWalkingDynamicField(value) 方法,将 NativeRequest 设置到 Controller 的 EnhanceRequireObjectCache 的 nativeWebRequest 属性中。其中,NativeRequest 来自 InvokeForRequestInterceptor 拦截设置,而 EnhanceRequireObjectCache 来自 ControllerConstructorInterceptor 拦截设置。
      • 注意nativeWebRequest 属性,当前 Request 对象。因为一个 Controller 对应一个EnhanceRequireObjectCache 对象,nativeWebRequest 对象被多线程共享时会冲突,在 SkyWalking 5.x 版本会修改成 ThreadLocal 属性,解决并发问题

4.5 ControllerInstrumentation

org.skywalking.apm.plugin.spring.mvc.v4.define.ControllerInstrumentation ,实现 AbstractControllerInstrumentation 抽象类,定义了方法切面,代码如下:

  • 拦截 @Controller 注解的 Controller 类。

4.5.1 AbstractMethodInteceptor

org.skywalking.apm.plugin.spring.mvc.commons.interceptor.AbstractMethodInteceptor ,实现 InstanceMethodsAroundInterceptor 接口,AbstractControllerInstrumentation 的拦截器的抽象基类。代码如下:

  • #getRequestURL(Method) 抽象方法,获得方法对应的请求路径。
  • 总体逻辑和 Tomcat 的 TomcatInvokeInterceptor 基本类似。
  • #beforeMethod(...) 方法,创建 EntrySpan 对象。代码如下:
    • 第 49 至 55 行:获得请求地址。首先,从 EnhanceRequireObjectCache 缓存中获取;其次,调用 #getRequestURL(Method) 方法,从类+方法的注解获取,并缓存。
    • 第 58 至 64 行:解析 ContextCarrier 对象,用于跨进程的链路追踪。在 《SkyWalking 源码分析 —— Agent 收集 Trace 数据》「 3.2.3 ContextCarrier 」 有详细解析。
    • 第 67 行:调用 ContextManager#createEntrySpan(operationName, contextCarrier) 方法,创建 EntrySpan 对象。
      • 注意,大多数情况下,我们部署基于 SpringMVC 框架在 Tomcat 下,那 Tomcat 的 TomcatInvokeInterceptor 也会创建 EntrySpan 对象,而 AbstractMethodInteceptor 也会创建 EntrySpan 对象,会不会重复创建?在 《SkyWalking 源码分析 —— Agent 收集 Trace 数据》 有答案哟。
    • 第 70 至 71 行:设置 EntrySpan 对象的 url / http.method 标签键值对。
    • 第 74 行:设置 EntrySpan 对象的组件类型。
    • 第 77 行:设置 EntrySpan 对象的分层。
  • #afterMethod(...) 方法,完成 EntrySpan 对象。
    • 第 86 至 92 行:当返回状态码大于等于 400 时,标记 EntrySpan 发生异常,并设置 status_code 标签键值对。
    • 第 95 行:调用 ContextManager#stopSpan() 方法,完成 EntrySpan 对象。
  • #handleMethodException(...) 方法,处理异常。代码如下:
    • 第 104 行:调用 AbstractSpan#errorOccurred() 方法,标记 EntrySpan 对象发生异常。
    • 第 106 行:调用 AbstractSpan#log(Throwable) 方法,记录异常日志到 EntrySpan 对象。

4.5.2 RequestMappingMethodInterceptor

org.skywalking.apm.plugin.spring.mvc.commons.interceptor.RequestMappingMethodInterceptor ,实现 AbstractMethodInteceptor 抽象类,实现了 #getRequestURL(Method) 方法,生成 @RequestMapping 注解方法的请求路径。

4.5.3 RestMappingMethodInterceptor

org.skywalking.apm.plugin.spring.mvc.commons.interceptor.RestMappingMethodInterceptor ,实现 AbstractMethodInteceptor 抽象类,实现了 #getRequestURL(Method) 方法,生成 @GetMapping / @PostMapping / @PutMapping / @DeleteMapping / @PatchMapping 注解方法的请求路径。

4.6 RestControllerInstrumentation

类似 「 4.5 ControllerInstrumentation 」

org.skywalking.apm.plugin.spring.mvc.v4.define.RestControllerInstrumentation ,实现 AbstractControllerInstrumentation 抽象类,定义了方法切面,代码如下:

  • 拦截 @RestController 注解的 Controller 类。

5. mvc-annotation-3.x-plugin

类似 「 4. mvc-annotation-4.x-plugin 」

考虑到 Spring MVC 5.x 都出了,本小节就暂不解析了。

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » SkyWalking 源码解析 —— Agent 插件(三)之 SpringMVC

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏