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

栏目: Java · 发布时间: 6年前

内容简介:SkyWalking 源码分析 —— Agent 插件(三)之 SpringMVC

本文主要基于 SkyWalking 3.2.6 正式版

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

关注 微信公众号:【芋道源码】 有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有 源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言 将得到 认真 回复。 甚至不知道如何读源码也可以请教噢
  4. 新的 源码解析文章 实时 收到通知。 每周更新一篇左右
  5. 认真的 源码交流微信群。

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 抽象类,定义了方法切面,代码如下:

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

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 抽象类,定义了方法切面,代码如下:

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

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 对象。代码如下:

  • classPath 属性,类的请求路径。
  • methodPathMapping 属性,方法对象与请求路径的映射。
  • #addPathMapping(method, methodPath) 方法, 添加 方法对应的请求路径到映射。
  • #findPathMapping(method) 方法,从映射中, 查询 方法对应的请求路径。

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 包下共有 四种 拦截器,如下图:

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

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

4. mvc-annotation-4.x-plugin

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

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

我们整理如下:

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 抽象类,定义了方法切面,代码如下:

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

分成两部分:

  • ConstructorInterceptPoint 部分,拦截所有构造方法给「 4.2.1 ControllerConstructorInterceptor 」处理。
  • InstanceMethodsInterceptPoint 部分,根据不同的 Mapping 方法注解,拦截提交给 todo 或者 todo 处理。

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

4.2.1 ControllerConstructorInterceptor

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

  • #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 抽象类,定义了方法切面,代码如下:

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

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 抽象类,定义了方法切面,代码如下:

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

  • 拦截 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 抽象类,定义了方法切面,代码如下:

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

  • 拦截 @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) 方法,从类+方法的注解获取,并缓存。
    • 第 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 抽象类,定义了方法切面,代码如下:

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

  • 拦截 @RestController 注解的 Controller 类。

5. mvc-annotation-3.x-plugin

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

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

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

666. 彩蛋

Spring 的体系,真的是博大精深!被 core-patch 部分卡了好久,虽然现在还是有点模糊。

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

胖友,分享一波朋友圈可好!


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Seasoned Schemer

The Seasoned Schemer

Daniel P. Friedman、Matthias Felleisen / The MIT Press / 1995-12-21 / USD 38.00

drawings by Duane Bibbyforeword and afterword by Guy L. Steele Jr.The notion that "thinking about computing is one of the most exciting things the human mind can do" sets both The Little Schemer (form......一起来看看 《The Seasoned Schemer》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具