skywalking插件工作原理剖析
1. 官方插件二次开发
前面在介绍
skywalking-agent目录时,提到了它有一个插件目录,并支持动态的开发插件。其实skywalking默认已经提供大部分框架的插件了,一般情况下不需要额外开发新的插件,可以直接改造已有的插件,使其适配自己的业务。下面介绍如何二次开发SpringMVC插件以采集业务参数。
(1)下载插件源码
在skywalking 8.7.0及以前的版本,插件的源码是直接放在skywalking主项目中的。在8.7.0以后的版本,把插件移出去了。(这一点好坑,我在skywalking最新版本里找了好久没找到插件的源码)。
下面分析的源码来自于skywalking 8.7.0版本。
插件源码位置:skywalking/apm-sniffer/apm-sdk-plugin(下面有个spring-plugins目录,里面就是spring相关的插件)
由于我的项目中用的SpringMVC是5.x的版本,因此主要关注下图中标记的两个目录。

顺便提一句skywalking 8.7.0以后版本的源码:
- URL:https://github.com/apache/skywalking-java
- 插件源码位置:skywalking-java/apm-sniffer/apm-sdk-plugin
(2)由SpringMVC插件窥探整个框架
① SpringMVC插件模块一览

一眼看过去,眼前一黑。仔细看就会发现,这里面的类主要分两大类,以Instrumentation和Interceptor结尾。Interceptor结尾的一般是拦截器,熟悉java agent技术的同学一般会知道Instrumentation,这个其实就是agent技术中的核心类,它可以加载Class文件,甚至可以修改Class文件。
我们发现有两个类名比较熟悉:ControllerInstrumentation和RestControllerInstrumentation,Controller和RestController就是SpringMVC中常用的两个注解,本项目中使用的是RestController注解,就以此类为入口吧。
这里不对java agent对额外的说明了,想要看懂skywalking框架,必须要先了解java agent。
② RestControllerInstrumentation类源码
public class RestControllerInstrumentation extends AbstractControllerInstrumentation {
// 这玩意就是RestController注解
public static final String ENHANCE_ANNOTATION = "org.springframework.web.bind.annotation.RestController";
@Override
protected String[] getEnhanceAnnotations() {
return new String[] {ENHANCE_ANNOTATION};
}
}
可以发现,ENHANCE_ANNOTATION这个属性的值就是SpringMVC中那个注解类。enhance是增强的意思,很好理解,这里就是要增强RestController注解的功能。看下getEnhanceAnnotations()方法在哪里被调用了。
③ AbstractControllerInstrumentation类源码
AbstractControllerInstrumentation类是RestControllerInstrumentation的父类,在enhanceClass()方法中调用了getEnhanceAnnotations()方法。这样就串起来了,这里实际上就是增强了被RestController注解修饰的类的功能。那么到底是怎么增强的呢?
public abstract class AbstractControllerInstrumentation extends AbstractSpring5Instrumentation {
@Override
public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {// 忽略}
@Override
public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
// 这里声明了方法切入点,返回的是一个数组,说明可以有多个切入点
return new InstanceMethodsInterceptPoint[] {
new DeclaredInstanceMethodsInterceptPoint() {
@Override
public ElementMatcher<MethodDescription> getMethodsMatcher() {
// 这里是被RequestMapping注解的方法作为一个切入点
return byMethodInheritanceAnnotationMatcher(named("org.springframework.web.bind.annotation.RequestMapping"));
}
@Override
public String getMethodsInterceptor() {
// 这里是此切入点的拦截器
return Constants.REQUEST_MAPPING_METHOD_INTERCEPTOR;
}
@Override
public boolean isOverrideArgs() {
return false;
}
},
new DeclaredInstanceMethodsInterceptPoint() {
@Override
public ElementMatcher<MethodDescription> getMethodsMatcher() {
// 这里是被GetMapping、PostMapping等注解的方法作为一个切入点
return byMethodInheritanceAnnotationMatcher(named("org.springframework.web.bind.annotation.GetMapping"))
.or(byMethodInheritanceAnnotationMatcher(named("org.springframework.web.bind.annotation.PostMapping")))
.or(byMethodInheritanceAnnotationMatcher(named("org.springframework.web.bind.annotation.PutMapping")))
.or(byMethodInheritanceAnnotationMatcher(named("org.springframework.web.bind.annotation.DeleteMapping")))
.or(byMethodInheritanceAnnotationMatcher(named("org.springframework.web.bind.annotation.PatchMapping")));
}
@Override
public String getMethodsInterceptor() {
// 这里是此切入点的拦截器
return Constants.REST_MAPPING_METHOD_INTERCEPTOR;
}
@Override
public boolean isOverrideArgs() {
return false;
}
}
};
}
@Override
protected ClassMatch enhanceClass() {
// 这里是是增强类的功能
return ClassAnnotationMatch.byClassAnnotationMatch(getEnhanceAnnotations());
}
protected abstract String[] getEnhanceAnnotations();
}
public class Constants {
public static final String REQUEST_MAPPING_METHOD_INTERCEPTOR = "org.apache.skywalking.apm.plugin.spring.mvc.commons.interceptor.RequestMappingMethodInterceptor";
public static final String REST_MAPPING_METHOD_INTERCEPTOR = "org.apache.skywalking.apm.plugin.spring.mvc.commons.interceptor.RestMappingMethodInterceptor";
}
在getInstanceMethodsInterceptPoints()方法中,可以看到很多熟悉的注解,比如PostMapping。结合一下方法名,可以大胆的推测,这里其实就是定义对象实例方法的拦截切入点。再看下getMethodsInterceptor()方法中使用的两个常量,以REST_MAPPING_METHOD_INTERCEPTOR为例,它的值就是个类名:RestMappingMethodInterceptor,那这个类肯定就是负责增强功能的拦截器。进入这个拦截器瞧瞧。
④ RestMappingMethodInterceptor类源码
// 这里省略了大量的代码
public class RestMappingMethodInterceptor extends AbstractMethodInterceptor {
@Override
public String getRequestURL(Method method) {
// 这里是从注解中解析请求url
return ParsePathUtil.recursiveParseMethodAnnotation(method, m -> {
String requestURL = null;
GetMapping getMapping = AnnotationUtils.getAnnotation(m, GetMapping.class);
if (getMapping != null) {
if (getMapping.value().length > 0) {
requestURL = getMapping.value()[0];
} else if (getMapping.path().length > 0) {
requestURL = getMapping.path()[0];
}
}
return requestURL;
});
}
@Override
public String getAcceptedMethodTypes(Method method) {
// 这里是从注解中解析请求类型
return ParsePathUtil.recursiveParseMethodAnnotation(method, m -> {
if (AnnotationUtils.getAnnotation(m, GetMapping.class) != null) {
return "{GET}";
} else {
return null;
}
});
}
}
这个类里的两个方法都是工具方法,仅仅解析了请求url和类型。其他的功能实现肯定在它的父类AbstractMethodInterceptor里。
⑤ AbstractMethodInterceptor类源码
// 这里省略了大量的代码,只保留了核心的部分
public abstract class AbstractMethodInterceptor implements InstanceMethodsAroundInterceptor {
public abstract String getRequestURL(Method method);
public abstract String getAcceptedMethodTypes(Method method);
@Override
public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
MethodInterceptResult result) throws Throwable {
// 这是在被切入的方法执行前,要执行的逻辑
Object request = ContextManager.getRuntimeContext().get(REQUEST_KEY_IN_RUNTIME_CONTEXT);
if (request != null) {
StackDepth stackDepth = (StackDepth) ContextManager.getRuntimeContext().get(CONTROLLER_METHOD_STACK_DEPTH);
if (stackDepth == null) {
final ContextCarrier contextCarrier = new ContextCarrier();
// 如果请求类是继承自servlet-api提供的HttpServletRequest类,则走此方法
if (IN_SERVLET_CONTAINER && HttpServletRequest.class.isAssignableFrom(request.getClass())) {
final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
// AbstractSpan是skywalking中日志的一个载体,用于采集数据
AbstractSpan span = ContextManager.createEntrySpan(operationName, contextCarrier);
// 采集请求URL
Tags.URL.set(span, httpServletRequest.getRequestURL().toString());
// 采集请求的类型,如GET、POST
Tags.HTTP.METHOD.set(span, httpServletRequest.getMethod());
// 标记是SpringMVC的日志
span.setComponent(ComponentsDefine.SPRING_MVC_ANNOTATION);
// 标记是HTTP请求
SpanLayer.asHttp(span);
if (SpringMVCPluginConfig.Plugin.SpringMVC.COLLECT_HTTP_PARAMS) {
// 采集请求参数
RequestUtil.collectHttpParam(httpServletRequest, span);
}
if (!CollectionUtil.isEmpty(SpringMVCPluginConfig.Plugin.Http.INCLUDE_HTTP_HEADERS)) {
// 采集请求头
RequestUtil.collectHttpHeaders(httpServletRequest, span);
}
} else {
throw new IllegalStateException("this line should not be reached");
}
stackDepth = new StackDepth();
ContextManager.getRuntimeContext().put(CONTROLLER_METHOD_STACK_DEPTH, stackDepth);
}
stackDepth.increment();
}
}
@Override
public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
Object ret) throws Throwable {
// 这是在被切入的方法执行后,要执行的逻辑
}
}
这个类中就是增强的功能,可以看出它能在被切入的方法之前和之后增强功能,在被请求注解类(如PostMapping)注解的方法执行前,它可以采集到请求的url、请求头、请求内容等等。那么在方法执行后,它肯定也可以采集到响应内容。
⑥ 小结
看到这里,SpringMVC插件采集的原理基本上可以猜到了,就是实现了拦截器,拦截了被注解的请求接口方法,并在方法执行前后采集数据。
且先不管这个拦截器到底是怎么工作的,看到这里,基本上已经明了该如何采集自定义的业务数据了,就是直接修改AbstractMethodInterceptor源码即可。
(3)业务请求参数采集
① 默认的请求参数采集
if (SpringMVCPluginConfig.Plugin.SpringMVC.COLLECT_HTTP_PARAMS) {
// 采集请求参数
RequestUtil.collectHttpParam(serverHttpRequest, span);
}
看到采集参数的条件是COLLECT_HTTP_PARAMS,点进去看,好像是个配置类,默认是关闭的,那可以先把它打开。
public class SpringMVCPluginConfig {
public static class Plugin {
@PluginConfig(root = SpringMVCPluginConfig.class)
public static class SpringMVC {
// 是否采集请求参数的开关,默认是关闭的
public static boolean COLLECT_HTTP_PARAMS = false;
}
}
}
去skywalking-agent/config/agent.config配置文件中搜一下,果然找到了:
# 采集SpringMVC请求参数的开关
plugin.springmvc.collect_http_params=${SW_PLUGIN_SPRINGMVC_COLLECT_HTTP_PARAMS:false}
然后再看采集的方法RequestUtil.collectHttpParam(serverHttpRequest, span):
public class RequestUtil {
public static void collectHttpParam(HttpServletRequest request, AbstractSpan span) {
// 获取请求参数
final Map<String, String[]> parameterMap = request.getParameterMap();
if (parameterMap != null && !parameterMap.isEmpty()) {
String tagValue = CollectionUtil.toString(parameterMap);
tagValue = SpringMVCPluginConfig.Plugin.Http.HTTP_PARAMS_LENGTH_THRESHOLD > 0 ?
StringUtil.cut(tagValue, SpringMVCPluginConfig.Plugin.Http.HTTP_PARAMS_LENGTH_THRESHOLD) : tagValue;
// 将请求参数写入日志http.params字段中
Tags.HTTP.PARAMS.set(span, tagValue);
}
}
}
可以看到,获取请求参数是用的request.getParameterMap()方法,如果你的接口使用的表单方式提交,那么恭喜,参数可以被采集起来。如果使用application/json协议提交参数,不好意思,它采集不到。我的项目里都是后者,因此要额外开发。
② 自定义业务参数采集
使用application/json协议提交的参数,是不方便从request中直接解析出来的。而这里的Object[] allArguments是切入点方法的入参,刚好就是请求参数,因此这里就直接利用其解析请求参数。
if (SpringMVCPluginConfig.Plugin.SpringMVC.COLLECT_HTTP_PARAMS) {
if (!StringUtils.isEmpty(httpServletRequest.getContentType())
&& httpServletRequest.getContentType().contains(MediaType.APPLICATION_JSON_VALUE)) {
// 采集使用application/json协议提交的参数
recordJsonReqLog(allArguments, span, httpServletRequest.getHeader("X_TRACEID"));
} else {
RequestUtil.collectHttpParam(httpServletRequest, span);
}
}
// 记录业务参数的方法
private void recordJsonReqLog(Object[] allArguments, AbstractSpan span, String traceId) {
// 记录业务的调用链ID
if (!StringUtils.isEmpty(traceId)) {
span.tag(new StringTag("traceId"), traceId);
}
if (allArguments != null && allArguments.length > 0) {
// 记录请求参数
String param = GSON.toJson(allArguments[0]);
span.tag(new StringTag("http.req"), param);
try {
// 解析请求参数
JsonObject jsonObject = GSON.fromJson(param, JsonObject.class);
JsonObject data = jsonObject.getAsJsonObject("data");
if (data == null) {
// 如果没有data参数,直接解析外层参数
data = jsonObject;
}
// 记录业务参数
Optional.ofNullable(data.get("account")).ifPresent(jsonElement
-> span.tag(new StringTag("account"), jsonElement.getAsString()));
Optional.ofNullable(data.get("userId")).ifPresent(jsonElement
-> span.tag(new StringTag("userId"), jsonElement.getAsString()));
Optional.ofNullable(data.get("deviceId")).ifPresent(jsonElement
-> span.tag(new StringTag("deviceId"), jsonElement.getAsString()));
} catch (JsonSyntaxException e) {
}
}
}
采集响应参数的思路和上面一样,这里不做介绍了。
(4)使用插件
上面修改的是AbstractMethodInterceptor类,这个类所在模块为apm-springmvc-annotation-commons,所以直接使用maven命令打包,将生成的产物apm-springmvc-annotation-commons-8.7.0.jar拷贝到skywalking-agent/plugins目录下,然后重新构建项目并部署,即可生效。
(5)小结
看到这里,基本上我们已经学会skywalking的一些高级用法了,可以做一些简单的插件二次开发,足以应对项目中大部分业务数据的采集。其他的插件和SpringMVC插件类似,代码的结构基本上差不多。
2. 插件原理剖析
上面的SpringMVC插件源码,我们跟到拦截器
Interceptor,就停止了,那么拦截器到底是如何加载的呢?值得好好研究研究。(不得不说,skywalkig的源码写得是真的很复杂,但是确实很牛B。)在研究之前,我要先介绍一下Byte Buddy。
(1)Byte Buddy介绍
runtime code generation for the Java virtual machine
上面这段话摘自github上该项目的简介,翻译过来就是针对JVM虚拟机的运行时代码生成。这不就是动态代理么?话不多说,先上一段代码:
// agent探针类
public class ToStringAgent {
// 探针入口
public static void premain(String arguments, Instrumentation instrumentation) {
// AgentBuilder是Byte Buddy中的一个构建器
new AgentBuilder.Default()
// 拦截被ToString注解的类
.type(isAnnotatedWith(ToString.class))
// 被拦截的类要增强的功能
.transform(new AgentBuilder.Transformer() {
// DynamicType.Builder是Byte Buddy中用于生成代码的一个重要的构建器
@Override
public DynamicType.Builder transform(DynamicType.Builder builder,
TypeDescription typeDescription,
ClassLoader classloader) {
// 拦截名称为toString的方法
return builder.method(ElementMatchers.named("toString"))
// 使用ToStringInterceptor拦截器增强toString方法的功能
.intercept(MethodDelegation.to(ToStringInterceptor.class));
}
}).installOn(instrumentation);
}
}
// 拦截器
public class ToStringInterceptor {
// RuntimeType是Byte Buddy中应用于拦截器的注解,被它注解的方法就是运行时拦截器要执行的目标方法
@RuntimeType
// Origin注解的就是被代理的目标方法,AllArguments注解的是被代理的目标方法的参数,SuperCall注解的是被代理方法的调用器
public static Object intercept(@Origin Method method, @AllArguments Object[] args, @SuperCall Callable<?> callable)
throws Exception {
System.out.println("被代理的方法执行前,执行了拦截器");
try {
// 执行被代理的方法
return callable.call();
} finally {
System.out.println("被代理的方法执行后,执行了拦截器");
}
}
}
这段代码,要使用java agent技术来应用到项目中。它实现的功能就是,拦截项目中被ToString注解的类中的toString()方法,拦截器可以拿到目标方法执行时的所有信息,包括方法的入参,并且在目标方法执行前后,执行一段额外的逻辑。
看这实现的功能是不是有点眼熟,这不就是Lombok中注解实现的功能么?
(2)skywalking插件执行原理
在1-2节中,我们跟源码跟到了AbstractMethodInterceptor类,继续从这个类入手。
// 这里省略了大量的代码,只保留了核心的部分
public abstract class AbstractMethodInterceptor implements InstanceMethodsAroundInterceptor {
@Override
public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
MethodInterceptResult result) throws Throwable {
// 这是在被切入的方法执行前,要执行的逻辑
}
@Override
public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
Object ret) throws Throwable {
// 这是在被切入的方法执行后,要执行的逻辑
}
}
① beforeMethod()方法在哪里调用的?

这里有好几个类都调用了beforeMethod()方法,这里我直接揭晓答案了,SpringMVC的拦截器走的是InstMethodsInter。下面那个带OverrideArgs的类,和1-2节中AbstractControllerInstrumentation类中构建拦截器切入点中的isOverrideArgs()方法应该有关,由于SpringMVC拦截器该方法返回的是false,因此不看这个类。
② 核心拦截器InstMethodsInter
public class InstMethodsInter {
private static final ILog LOGGER = LogManager.getLogger(InstMethodsInter.class);
private InstanceMethodsAroundInterceptor interceptor;
// 构造方法
public InstMethodsInter(String instanceMethodsAroundInterceptorClassName, ClassLoader classLoader) {
try {
// 使用类加载器加载拦截器到JVM中。这里要加载的原因是,插件拦截器都在独立的jar包,不在agent主程序里
interceptor = InterceptorInstanceLoader.load(instanceMethodsAroundInterceptorClassName, classLoader);
} catch (Throwable t) {
throw new PluginException("Can't create InstanceMethodsAroundInterceptor.", t);
}
}
// 拦截器的主方法,看到RuntimeType、AllArguments这些注解没,都是Byte Buddy里的
@RuntimeType
public Object intercept(@This Object obj, @AllArguments Object[] allArguments, @SuperCall Callable<?> zuper,
@Origin Method method) throws Throwable {
EnhancedInstance targetObject = (EnhancedInstance) obj;
MethodInterceptResult result = new MethodInterceptResult();
try {
// 拦截器在代理方法执行前执行beforeMethod方法
interceptor.beforeMethod(targetObject, method, allArguments, method.getParameterTypes(), result);
} catch (Throwable t) {
LOGGER.error(t, "class[{}] before method[{}] intercept failure", obj.getClass(), method.getName());
}
// 接收被代理的方法执行的结果
Object ret = null;
try {
if (!result.isContinue()) {
ret = result._ret();
} else {
// 执行被代理的方法
ret = zuper.call();
}
} catch (Throwable t) {
try {
// 拦截器捕获被代理的方法的异常
interceptor.handleMethodException(targetObject, method, allArguments, method.getParameterTypes(), t);
} catch (Throwable t2) {
LOGGER.error(t2, "class[{}] handle method[{}] exception failure", obj.getClass(), method.getName());
}
throw t;
} finally {
try {
// 拦截器在代理方法执行后执行afterMethod方法
ret = interceptor.afterMethod(targetObject, method, allArguments, method.getParameterTypes(), ret);
} catch (Throwable t) {
LOGGER.error(t, "class[{}] after method[{}] intercept failure", obj.getClass(), method.getName());
}
}
return ret;
}
}
看到@RuntimeType注解就松了一口气了,这就是Byte Buddy中拦截器的写法。
③ 核心拦截器在哪里用的?
直接点击InstMethodsInter类,看它在哪里用到了:
// 这里仅保留了核心代码
public abstract class ClassEnhancePluginDefine extends AbstractClassEnhancePluginDefine {
private static final ILog LOGGER = LogManager.getLogger(ClassEnhancePluginDefine.class);
// 增强类实例的核心方法
protected DynamicType.Builder<?> enhanceInstance(TypeDescription typeDescription,
DynamicType.Builder<?> newClassBuilder, ClassLoader classLoader,
EnhanceContext context) throws PluginException {
// 获取实例类方法拦截切入点
InstanceMethodsInterceptPoint[] instanceMethodsInterceptPoints = getInstanceMethodsInterceptPoints();
boolean existedMethodsInterceptPoints = false;
if (instanceMethodsInterceptPoints != null && instanceMethodsInterceptPoints.length > 0) {
existedMethodsInterceptPoints = true;
}
if (existedConstructorInterceptPoint) {
// 增强构造方法
}
if (existedMethodsInterceptPoints) {
// 增强实例类方法
for (InstanceMethodsInterceptPoint instanceMethodsInterceptPoint : instanceMethodsInterceptPoints) {
// 判断是否属于要拦截的目标方法的条件
ElementMatcher.Junction<MethodDescription> junction = not(isStatic())
// getMethodsMatcher()在上面1-2节也提到了
.and(instanceMethodsInterceptPoint.getMethodsMatcher());
// 这里就是Byte Buddy中构建方法拦截器的核心写法。
newClassBuilder = newClassBuilder.method(junction)
// 设置拦截器
.intercept(MethodDelegation.withDefaultConfiguration()
.to(new InstMethodsInter(interceptor, classLoader)));
}
}
return newClassBuilder;
}
}
这个类中可以看出,它将拦截器与被拦截的类编织到一起了。继续跟踪enhanceInstance()方法在哪里调用,一路找过去,最终到了Transformer类。
④ SkyWalkingAgent探针的入口
最终跟到了SkyWalkingAgent.Transformer类,看到这里,基本上就明白了:
public class SkyWalkingAgent {
private static ILog LOGGER = LogManager.getLogger(SkyWalkingAgent.class);
// Agent探针的入口
public static void premain(String agentArgs, Instrumentation instrumentation) throws PluginException {
// 加载插件
final PluginFinder pluginFinder = new PluginFinder(new PluginBootstrap().loadPlugins());
// Byte Buddy的构建器
AgentBuilder agentBuilder = new AgentBuilder...;
// type()判断是否需要由插件拦截
agentBuilder.type(pluginFinder.buildMatch())
// 这里就是设置拦截器
.transform(new Transformer(pluginFinder))
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.with(new RedefinitionListener())
.with(new Listener())
.installOn(instrumentation);
}
// Byte Buddy的拦截器
private static class Transformer implements AgentBuilder.Transformer {
@Override
public DynamicType.Builder<?> transform(final DynamicType.Builder<?> builder,
final TypeDescription typeDescription,
final ClassLoader classLoader,
final JavaModule module) {
LoadedLibraryCollector.registerURLClassLoader(classLoader);
// 取出插件
List<AbstractClassEnhancePluginDefine> pluginDefines = pluginFinder.find(typeDescription);
if (pluginDefines.size() > 0) {
// Byte Buddy中用于生成代码的一个重要的构建器
DynamicType.Builder<?> newBuilder = builder;
EnhanceContext context = new EnhanceContext();
// 遍历插件
for (AbstractClassEnhancePluginDefine define : pluginDefines) {
// 在这里加载插件中的拦截器,最终调用了上面提到的ClassEnhancePluginDefine.enhanceInstance()方法
DynamicType.Builder<?> possibleNewBuilder = define.define(
typeDescription, newBuilder, classLoader, context);
if (possibleNewBuilder != null) {
newBuilder = possibleNewBuilder;
}
}
return newBuilder;
}
return builder;
}
}
}
在skywalking探针的入口方法中,就已经加载了所有插件的拦截器,并将拦截器和需要拦截的方法关联到了一起,剩下的功能,就交给Byte Buddy了。
(3)小结
看到这里,skywalking探针工作的原理,就已经清楚了。skywalking是利用了java agent和Byte Buddy两项技术,来实现无侵入式的拦截。如果不了解这两项技术,直接看源码会一脸懵B。
skywalking插件工作原理剖析的更多相关文章
- NameNode和SecondaryNameNode工作原理剖析
NameNode和SecondaryNameNode工作原理剖析 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.NameNode中的元数据是存储在那里的? 1>.首先,我 ...
- NameNode与DataNode的工作原理剖析
NameNode与DataNode的工作原理剖析 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.HDFS写数据流程 >.客户端通过Distributed FileSyst ...
- Spring Boot 揭秘与实战 源码分析 - 工作原理剖析
文章目录 1. EnableAutoConfiguration 帮助我们做了什么 2. 配置参数类 – FreeMarkerProperties 3. 自动配置类 – FreeMarkerAutoCo ...
- 46、Spark SQL工作原理剖析以及性能优化
一.工作原理剖析 1.图解 二.性能优化 1.设置Shuffle过程中的并行度:spark.sql.shuffle.partitions(SQLContext.setConf()) 2.在Hive数据 ...
- CDN 工作原理剖析
CDN 工作原理剖析 CDN / Content Delivery Network / 内容分发网络 https://www.cloudflare.com/zh-cn/learning/cdn/wha ...
- 嵌入式Linux之我行——ARM MMU工作原理剖析【转】
转自:http://blog.chinaunix.net/uid-20698426-id-136197.html 一.MMU的产生 许多年以前,当人们还在使用DOS或是更古老的操作系统的时 ...
- 原理剖析-Netty之服务端启动工作原理分析(下)
一.大致介绍 1.由于篇幅过长难以发布,所以本章节接着上一节来的,上一章节为[原理剖析(第 010 篇)Netty之服务端启动工作原理分析(上)]: 2.那么本章节就继续分析Netty的服务端启动,分 ...
- petite-vue-源码剖析-v-for重新渲染工作原理
在<petite-vue源码剖析-v-if和v-for的工作原理>我们了解到v-for在静态视图中的工作原理,而这里我们将深入了解在更新渲染时v-for是如何运作的. 逐行解析 // 文件 ...
- petite-vue源码剖析-双向绑定`v-model`的工作原理
前言 双向绑定v-model不仅仅是对可编辑HTML元素(select, input, textarea和附带[contenteditable=true])同时附加v-bind和v-on,而且还能利用 ...
- petite-vue源码剖析-ref的工作原理
ref内部的工作原理十分简单,其实就是将指令ref.:ref或v-bind:ref标识的元素实例存储到当前作用域的$refs对象中,那么我们就可以通过this.$refs获取对应的元素实例.但由于作用 ...
随机推荐
- 2.go语言基础类型漫游
本篇前瞻 本篇是go语言的基础篇,主要是帮助大家梳理一下go语言的基本类型,注意本篇有参考go圣经,如果你有完整学习的需求可以看一下,另外,go语言的基本类型比较简单,介绍过程就比较粗暴. 基本类型 ...
- Solution -「CSP 2019」Partition
Description Link. 给出一个数列,要求将其分成几段,使每段的和非严格递增,求最小的每段的和的平方和. Solution 注意到 \(a_{i}\) 为正,对于正数有这样的结论: \[a ...
- 痞子衡嵌入式:MCUBootUtility v5.3发布,利用XMCD轻松使能外部RAM
-- 痞子衡维护的 NXP-MCUBootUtility 工具距离上一个大版本(v5.0.0)发布过去4个多月了,期间痞子衡也做过三个小版本更新,但不足以单独介绍.这一次痞子衡为大家带来了全新重要版本 ...
- Python基础知识——函数的基本使用、函数的参数、名称空间与作用域、函数对象与闭包、 装饰器、迭代器、生成器与yield、函数递归、面向过程与函数式(map、reduce、filter)
文章目录 1 函数的基本使用 一 引入 二 定义函数 三 调用函数与函数返回值 2 函数的参数 一 形参与实参介绍 二 形参与实参的具体使用 2.1 位置参数 2.2 关键字参数 2.3 默认参数 2 ...
- 10. 用Rust手把手编写一个wmproxy(代理,内网穿透等), HTTP内网穿透支持修改头信息
用Rust手把手编写一个wmproxy(代理,内网穿透等), HTTP内网穿透支持修改头信息 项目 ++wmproxy++ gite: https://gitee.com/tickbh/wmproxy ...
- Python - 读取CSV文件发现有重复数据,如何清洗以及保存为CSV文件,这里有完整的过程!!!! 片尾有彩蛋
语言:Python 功能: 1.清洗CSV文件中重复数据. 2.保存为CSV文件 大体流程: 1.首先观察CSV文件中的数据布局格式如何? 2.通过csv包读取数据.并根据规则使用continue,来 ...
- nittest单元测试框架—加载测试用例的3种方法以及测试报告存储管理
项目结构 测试用例 import unittest class LoginTestCase(unittest.TestCase): def test_login_success(self): self ...
- 解密Prompt系列17. LLM对齐方案再升级 WizardLM & BackTranslation & SELF-ALIGN
话接上文的指令微调的样本优化方案,上一章是通过多样性筛选和质量过滤,对样本量进行缩减,主打经济实惠.这一章是通过扩写,改写,以及回译等半监督样本挖掘方案对种子样本进行扩充,提高种子指令样本的多样性和复 ...
- 优化预算管理流程:Web端实现预算编制的利器
本文由葡萄城技术团队原创并首发.转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 前言:什么是预算和预算编制 预算 预算是企业在预测.决策的基础上,以数量和金 ...
- 文心一言 VS 讯飞星火 VS chatgpt (119)-- 算法导论10.3 4题
四.用go语言,我们往往希望双向链表的所有元素在存储器中保持紧凑,例如,在多数组表示中占用前m 个下标位置.(在页式虚拟存储的计算环境下,即为这种情况.)假设除指向链表本身的指针外没有其他指针指向该链 ...