skywalking插件工作原理剖析

发布时间 2023-03-31 10:59:01作者: 风の伤

1. 官方插件二次开发

前面在介绍skywalking-agent目录时,提到了它有一个插件目录,并支持动态的开发插件。其实skywalking默认已经提供大部分框架的插件了,一般情况下不需要额外开发新的插件,可以直接改造已有的插件,使其适配自己的业务。

下面介绍如何二次开发SpringMVC插件以采集业务参数。

(1)下载插件源码

在skywalking 8.7.0及以前的版本,插件的源码是直接放在skywalking主项目中的。在8.7.0以后的版本,把插件移出去了。(这一点好坑,我在skywalking最新版本里找了好久没找到插件的源码)。

下面分析的源码来自于skywalking 8.7.0版本。

由于我的项目中用的SpringMVC是5.x的版本,因此主要关注下图中标记的两个目录。

顺便提一句skywalking 8.7.0以后版本的源码:
- URL:https://github.com/apache/skywalking-java
- 插件源码位置:skywalking-java/apm-sniffer/apm-sdk-plugin

(2)由SpringMVC插件窥探整个框架

① SpringMVC插件模块一览

一眼看过去,眼前一黑。仔细看就会发现,这里面的类主要分两大类,以InstrumentationInterceptor结尾。Interceptor结尾的一般是拦截器,熟悉java agent技术的同学一般会知道Instrumentation,这个其实就是agent技术中的核心类,它可以加载Class文件,甚至可以修改Class文件。

我们发现有两个类名比较熟悉:ControllerInstrumentationRestControllerInstrumentationControllerRestController就是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 agentByte Buddy两项技术,来实现无侵入式的拦截。如果不了解这两项技术,直接看源码会一脸懵B。