摘要: 原创出处 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
。
- 第 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 接口,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 抽象类,定义了方法切面,代码如下:
- 拦截
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 都出了,本小节就暂不解析了。
