内容简介: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
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
法律程序的意义:对中国法制建设的另一种思考
季卫东 / 中国法制出版社 / 2005-1 / 10.0
《法律程序的意义:对中国法制建设的另一种思考》内容为现代程序的概念与特征、现代程序的结构与功能、程序与现代社会、中国法律程序的缺陷、程序建设的程序等。一起来看看 《法律程序的意义:对中国法制建设的另一种思考》 这本书的介绍吧!