关注 微信公众号:【芋道源码】 有福利:
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 部分卡了好久,虽然现在还是有点模糊。
胖友,分享一波朋友圈可好!