内容简介:SkyWalking 源码分析 —— Agent 插件(三)之 SpringMVC
本文主要基于 SkyWalking 3.2.6 正式版
- 1. 概述
- 2. core-patch
- 3. mvc-annotation-commons
- 4. mvc-annotation-4.x-plugin
- 5. mvc-annotation-3.x-plugin
- 666. 彩蛋
关注 微信公众号:【芋道源码】 有福利:
- RocketMQ / MyCAT / Sharding-JDBC 所有 源码分析文章列表
- RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
- 您对于源码的疑问每条留言 都 将得到 认真 回复。 甚至不知道如何读源码也可以请教噢 。
- 新的 源码解析文章 实时 收到通知。 每周更新一篇左右 。
- 认真的 源码交流微信群。
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
。
- 第 47 行:若目标类实现了 EnhancedInstance 接口,返回
2.2 AutowiredAnnotationProcessorInstrumentation
原因和目的,参见 Issue#622 和 Issue#624 。
Spring 的 AutowiredAnnotationBeanPostProcessor#determineCandidateConstructors(Class, String)
方法,返回有三种情况:
- 带有
@Autowired
参数的构造方法 - 仅有一个带参数的构造方法
- 不带参数的构造方法
因为 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 增强生成 )。
- 第 115 行:创建类与构造方法的映射
-
#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 行:返回结果。
- 第 54 行:若
ps:这块略复杂,如果笔者未解释清晰,那是因为我菜。
3. mvc-annotation-commons
mvc-annotation-commons
模块,提供公用代码,提供给 mvc-annotation-4.x-plugin
和 mvc-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
包下共有 四种 拦截器,如下图:
结合「 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 抽象类,定义了方法切面,代码如下:
分成两部分:
- 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 抽象类,定义了方法切面,代码如下:
- 拦截
InvocableHandlerMethod#invokeForRequest(NativeWebRequest, ModelAndViewContainer, Object... providedArgs)
方法,提交给 InvokeForRequestInterceptor 处理。
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 类。
-
- 第 42 行:调用
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 属性,解决并发问题 。
- 注意 ,
- 第 44 至 48 行: 调用
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 对象的分层。
- 第 49 至 55 行:获得请求地址。首先,从 EnhanceRequireObjectCache 缓存中获取;其次,调用
-
#afterMethod(...)
方法,完成 EntrySpan 对象。- 第 86 至 92 行:当返回状态码大于等于 400 时,标记 EntrySpan 发生异常,并设置
status_code
标签键值对。 - 第 95 行:调用
ContextManager#stopSpan()
方法,完成 EntrySpan 对象。
- 第 86 至 92 行:当返回状态码大于等于 400 时,标记 EntrySpan 发生异常,并设置
-
#handleMethodException(...)
方法,处理异常。代码如下:- 第 104 行:调用
AbstractSpan#errorOccurred()
方法,标记 EntrySpan 对象发生异常。 - 第 106 行:调用
AbstractSpan#log(Throwable)
方法,记录异常日志到 EntrySpan 对象。
- 第 104 行:调用
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
部分卡了好久,虽然现在还是有点模糊。
胖友,分享一波朋友圈可好!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 急速 debug 实战三(Node - webpack插件,babel插件,vue源码篇)
- Cloud Studio 优秀插件一览 | 附源码
- Android 插件化框架 DynamicLoadApk 源码分析
- 每日一博 | 深入分析 源码级别解读 MyBatis 插件
- SkyWalking 源码分析 —— Agent 插件(一)之 Tomcat
- SkyWalking 源码分析 —— Agent 插件(二)之 Dubbo
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
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》 这本书的介绍吧!