关注 微信公众号:【芋道源码】 有福利:
core-patch
模块,给 Spring 打补丁,解决因为 Agent 对类的增强操作导致的冲突。
打脸提示:笔者对 Spring 的一些( 大部分 )机制了解的较浅薄,所以本小节更多的是粘贴代码 + 相关 Issue 。
原因和目的,参见 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(...)
方法,代码如下:
true
。 ret
。 原因和目的,参见 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(...)
方法,创建类与构造方法的映射。代码如下:
candidateConstructorsCache
, 用于缓存 。 #afterMethod(...)
方法,处理自动实现 EnhancedInstance 接口的类,和 Spring 的 AutowiredAnnotationBeanPostProcessor#determineCandidateConstructors(Class, String)
的冲突。代码如下:
beanClass
实现了 EnhancedInstance 接口。 candidateConstructorsCache
缓存中获得构造方法。 beanClass
的类的构造方法,缓存并返回。
ret == null
原本方法没找到构造方法,存在冲突 —– ret != null
原本方法就找到构造方法,不存在冲突 —– candidateConstructorsCache
中。 ps:这块略复杂,如果笔者未解释清晰,那是因为我菜。
mvc-annotation-commons
模块,提供公用代码,提供给 mvc-annotation-4.x-plugin
和 mvc-annotation-3.x-plugin
使用。
org.skywalking.apm.plugin.spring.mvc.commons.PathMappingCache
,缓存 Controller 的所有请求路径,一个 Controller 对象一个 PathMappingCache 对象。代码如下:
classPath
属性,类的请求路径。 methodPathMapping
属性,方法对象与请求路径的映射。 #addPathMapping(method, methodPath)
方法, 添加 方法对应的请求路径到映射。 #findPathMapping(method)
方法,从映射中, 查询 方法对应的请求路径。 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 属性,解决并发问题 。 org.skywalking.apm.plugin.spring.mvc.commons.Constants
,枚举 org.skywalking.apm.plugin.spring.mvc.commons.interceptor
包下的拦截器类名。
org.skywalking.apm.plugin.spring.mvc.commons.interceptor
包下共有 四种 拦截器,如下图:
结合「 4. mvc-annotation-4.x-plugin 」,我们一起分享。
本小节涉及到的类如下图:
我们整理如下:
Instrumentation | Interceptor | |
---|---|---|
AbstractControllerInstrumentation | ControllerConstructorInterceptor | |
InvocableHandlerInstrumentation | InvokeForRequestInterceptor | |
HandlerMethodInstrumentation | GetBeanInterceptor | |
ControllerInstrumentation | RequestMappingMethodInterceptor | |
RestControllerInstrumentation | RestMappingMethodInterceptor |
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 插件体系》 有详细解析。
org.skywalking.apm.plugin.spring.mvc.v4.define.AbstractControllerInstrumentation
,实现 AbstractSpring4Instrumentation 抽象类,定义了方法切面,代码如下:
分成两部分:
AbstractControllerInstrumentation 是一个 抽象基类 ,有「 4.5 ControllerInstrumentation 」,「 4.6 RestControllerInstrumentation 」两个子类,实现 #getEnhanceAnnotations()
抽象方法,返回 不同 的类注解,从而拦截不同的类。
org.skywalking.apm.plugin.spring.mvc.v4.ControllerConstructorInterceptor
,实现 InstanceConstructorInterceptor 接口,Abstract Controller Instrumentation 的拦截器。代码如下:
#onConstruct(...)
方法,代码如下:
EnhancedInstance#setSkyWalkingDynamicField(value)
方法,设置到 Controller 的私有变量( SkyWalking 自动生成 )。即,Controller : EnhanceRequireObjectCache = 1 : 1
。 org.skywalking.apm.plugin.spring.mvc.v4.define.InvocableHandlerInstrumentation
,实现 AbstractSpring4Instrumentation 抽象类,定义了方法切面,代码如下:
InvocableHandlerMethod#invokeForRequest(NativeWebRequest, ModelAndViewContainer, Object... providedArgs)
方法,提交给 InvokeForRequestInterceptor 处理。 org.skywalking.apm.plugin.spring.mvc.commons.interceptor.InvokeForRequestInterceptor
,实现 InstanceMethodsAroundInterceptor 接口,InvocableHandlerInstrumentation 的拦截器。代码如下:
#beforeMethod(...)
方法,代码如下:
EnhancedInstance#setSkyWalkingDynamicField(value)
方法,设置 NativeWebRequest 到 ServletInvocableHandlerMethod 的私有变量( SkyWalking 自动生成 )。
objInst
类型为 ServletInvocableHandlerMethod 类( 继承 InvocableHandlerMethod 类 ),每次请求都会创建新的该对象,因此设置 NativeWebRequest 对象,线程安全。 allArguments[0]
类型为 NativeWebRequest 类。 org.skywalking.apm.plugin.spring.mvc.v4.define.HandlerMethodInstrumentation
,实现 AbstractSpring4Instrumentation 抽象类,定义了方法切面,代码如下:
HandlerMethod#getBean()
方法,提交给 GetBeanInterceptor 处理。 org.skywalking.apm.plugin.spring.mvc.commons.interceptor.GetBeanInterceptor
,实现 InstanceMethodsAroundInterceptor 接口,InvocableHandlerInstrumentation 的拦截器。代码如下:
#afterMethod(...)
方法,代码如下:
EnhancedInstance#setSkyWalkingDynamicField(value)
方法,将 NativeRequest 设置到 Controller 的 EnhanceRequireObjectCache 的 nativeWebRequest
属性中。其中,NativeRequest 来自 InvokeForRequestInterceptor 拦截设置,而 EnhanceRequireObjectCache 来自 ControllerConstructorInterceptor 拦截设置。
nativeWebRequest
属性,当前 Request 对象。 因为一个 Controller 对应一个EnhanceRequireObjectCache 对象, nativeWebRequest
对象被多线程共享时会冲突,在 SkyWalking 5.x 版本会修改成 ThreadLocal 属性,解决并发问题 。 org.skywalking.apm.plugin.spring.mvc.v4.define.ControllerInstrumentation
,实现 AbstractControllerInstrumentation 抽象类,定义了方法切面,代码如下:
@Controller
注解的 Controller 类。 org.skywalking.apm.plugin.spring.mvc.commons.interceptor.AbstractMethodInteceptor
,实现 InstanceMethodsAroundInterceptor 接口,AbstractControllerInstrumentation 的拦截器的 抽象基类 。代码如下:
#getRequestURL(Method)
抽象方法 ,获得方法对应的请求路径。 #beforeMethod(...)
方法,创建 EntrySpan 对象。代码如下:
#getRequestURL(Method)
方法,从类+方法的注解获取,并缓存。
ContextManager#createEntrySpan(operationName, contextCarrier)
方法,创建 EntrySpan 对象。
url
/ http.method
标签键值对。 #afterMethod(...)
方法,完成 EntrySpan 对象。
status_code
标签键值对。 ContextManager#stopSpan()
方法,完成 EntrySpan 对象。 #handleMethodException(...)
方法,处理异常。代码如下:
AbstractSpan#errorOccurred()
方法,标记 EntrySpan 对象发生异常。 AbstractSpan#log(Throwable)
方法,记录异常日志到 EntrySpan 对象。 org.skywalking.apm.plugin.spring.mvc.commons.interceptor.RequestMappingMethodInterceptor
,实现 AbstractMethodInteceptor 抽象类,实现了 #getRequestURL(Method)
方法,生成 @RequestMapping
注解方法的请求路径。
org.skywalking.apm.plugin.spring.mvc.commons.interceptor.RestMappingMethodInterceptor
,实现 AbstractMethodInteceptor 抽象类,实现了 #getRequestURL(Method)
方法,生成 @GetMapping
/ @PostMapping
/ @PutMapping
/ @DeleteMapping
/ @PatchMapping
注解方法的请求路径。
类似「 4.5 ControllerInstrumentation 」。
org.skywalking.apm.plugin.spring.mvc.v4.define.RestControllerInstrumentation
,实现 AbstractControllerInstrumentation 抽象类,定义了方法切面,代码如下:
@RestController
注解的 Controller 类。 类似「 4. mvc-annotation-4.x-plugin 」。
考虑到 Spring MVC 5.x 都出了,本小节就暂不解析了。
Spring 的体系,真的是博大精深!被 core-patch
部分卡了好久,虽然现在还是有点模糊。
胖友,分享一波朋友圈可好!