⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

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

本文主要基于 SkyWalking 3.2.6 正式版


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

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


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 都出了,本小节就暂不解析了。

666. 彩蛋

知识星球

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

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

文章目录
  1. 1. 1. 概述
  2. 2. 2. core-patch
    1. 2.1. 2.1 AopProxyFactoryInstrumentation
    2. 2.2. 2.2 AutowiredAnnotationProcessorInstrumentation
  3. 3. 3. mvc-annotation-commons
    1. 3.1. 3.1 PathMappingCache
    2. 3.2. 3.2 EnhanceRequireObjectCache
    3. 3.3. 3.3 Constants
    4. 3.4. 3.4 拦截器
  4. 4. 4. mvc-annotation-4.x-plugin
    1. 4.1. 4.1 AbstractSpring4Instrumentation
    2. 4.2. 4.2 AbstractControllerInstrumentation
      1. 4.2.1. 4.2.1 ControllerConstructorInterceptor
    3. 4.3. 4.3 InvocableHandlerInstrumentation
      1. 4.3.1. 4.3.1 InvokeForRequestInterceptor
    4. 4.4. 4.4 HandlerMethodInstrumentation
      1. 4.4.1. 4.4.1 GetBeanInterceptor
    5. 4.5. 4.5 ControllerInstrumentation
      1. 4.5.1. 4.5.1 AbstractMethodInteceptor
      2. 4.5.2. 4.5.2 RequestMappingMethodInterceptor
      3. 4.5.3. 4.5.3 RestMappingMethodInterceptor
    6. 4.6. 4.6 RestControllerInstrumentation
  5. 5. 5. mvc-annotation-3.x-plugin
  6. 6. 666. 彩蛋