Apache ShenYu 是一个异步的,高性能的,跨语言的,响应式的 API 网关。

ShenYu网关中,Apache ShenYu 利用 Java Agent字节码增强 技术实现了无痕埋点,使得用户无需引入依赖即可接入第三方可观测性系统,获取 TracesMetricsLogging

本文基于shenyu-2.4.2版本进行源码分析,官网的介绍请参考 可观测性 。

具体而言,就是shenyu-agent模块,它基于 Java Agent 机制,通过ByteBuddy字节码增强库,在类加载时增强对象,属于静态代理。

  • AOP术语

在分析源码之前,介绍下AOP相关的术语,便于后续的理解:

  • JoinPoint:连接点,程序运行中的时间点,比如方法的执行点;

  • PointCut:切入点,匹配 JoinPoint 的条件;

  • Advice:通知,具体的执行逻辑;

  • Target:目标对象;

  • Proxy:代理对象。

  • 关于Byte Buddy

Byte Buddy是一个代码生成和操作库,在Java应用程序的运行期间创建和修改Java类。可以利用它创建任何类,不像JDK动态代理那样强制实现一个接口。此外,Byte Buddy提供了方便的API,用于手动、使用Java代理或在构建期间改变类。

  • 提供了非常方便的API接口,与强大的类,方法等匹配功能;
  • 开箱即用,零学习成本,屏蔽了底层操作字节码技术;
  • 强大的开放定制性功能,可以为任何实现的方法自定义字节码;
  • 最少运行时生成代码原则,性能高效;

1. premain入口

premain()函数javaagent 的入口函数,在 ShenYuShenyuAgentBootstrap 提供并实现整个agent的逻辑。

/*** agent 启动入口类*/
public class ShenyuAgentBootstrap {/*** 入口函数 premain.*/public static void premain(final String arguments, final Instrumentation instrumentation) throws Exception {// 1. 读取配置文件ShenyuAgentConfigUtils.setConfig(ShenyuAgentConfigLoader.load());// 2. 加载所有插件ShenyuAgentPluginLoader.getInstance().loadAllPlugins();// 3. 创建 agentAgentBuilder agentBuilder = new AgentBuilder.Default().with(new ByteBuddy().with(TypeValidation.ENABLED)).ignore(ElementMatchers.isSynthetic()).or(ElementMatchers.nameStartsWith("org.apache.shenyu.agent."));agentBuilder.type(ShenyuAgentTypeMatcher.getInstance()).transform(new ShenyuAgentTransformer()).with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION).with(new TransformListener()).installOn(instrumentation);// 4. 启动插件PluginLifecycleManager lifecycleManager = new PluginLifecycleManager();lifecycleManager.startup(ShenyuAgentConfigUtils.getPluginConfigMap());Runtime.getRuntime().addShutdownHook(new Thread(lifecycleManager::close));}
}

premain函数的核心逻辑,就是上面的四步操作:

    1. 读取配置文件;
    1. 加载所有插件;
    1. 创建 agent;
    1. 启动插件。

接下来的源码分析就依次分析这四个操作。

2. 读取配置文件

  • ShenyuAgentConfigLoader#load()

配置文件的处理由 ShenyuAgentConfigLoader 完成,代码实现如下:

public final class ShenyuAgentConfigLoader {// 配置文件路径private static final String CONFIG_PATH = "config-path";/*** 加载配置文件.*/public static ShenyuAgentConfig load() throws IOException {// 读取配置文件路径String configPath = System.getProperty(CONFIG_PATH);// 如果没有配置,就读取默认的文件 shenyu-agent.yamlFile configFile = StringUtils.isEmpty(configPath) ? ShenyuAgentLocator.locatorConf("shenyu-agent.yaml") : new File(configPath);// 读取配置文件并解析return ShenyuYamlEngine.agentConfig(configFile);}
}

可以通过config-path指定配置文件的路径,如果没有指定的话,就读取默认的配置文件 shenyu-agent.yaml,然后通过ShenyuYamlEngine来解析配置文件。

配置文件的格式是yaml格式,如何配置,请参考官网的介绍 可观测性 。

默认配置文件shenyu-agent.yaml的格式内容如下:

appName: shenyu-agent  # 指定一个名称
supports:  # 当前支持哪些功能tracing: # 链路追踪的插件
#    - jaeger
#    - opentelemetry- zipkinmetrics:  # 统计度量插件- logging:  # 日志信息插件- plugins:  # 每个插件的具体配置信息tracing:   # 链路追踪的插件jaeger:  # jaeger的相关配置host: "localhost"port: 5775props:SERVICE_NAME: "shenyu-agent"JAEGER_SAMPLER_TYPE: "const"JAEGER_SAMPLER_PARAM: "1"opentelemetry:  # opentelemetry的相关配置props:otel.traces.exporter: jaeger #zipkin #otlpotel.resource.attributes: "service.name=shenyu-agent"otel.exporter.jaeger.endpoint: "http://localhost:14250/api/traces"zipkin: # zipkin的相关配置host: "localhost"port: 9411props:SERVICE_NAME: "shenyu-agent"URL_VERSION: "/api/v2/spans"SAMPLER_TYPE: "const"SAMPLER_PARAM: "1"metrics:   # 统计度量插件prometheus: # prometheus的相关配置host: "localhost"port: 8081props:logging:   # 日志信息插件elasticSearch: # es的相关配置host: "localhost"port: 8082props:kafka:   # kafka的相关配置host: "localhost"port: 8082props:

需要开启哪个插件,就在supports中指定,然后再plugins指定插件的配置信息。

到目前为止,Apache ShenYu 发布的最新版本是2.4.2 版本,可以支持tracing的插件有jaegeropentelemetryzipkinmetricslogging将在后续的版本中陆续发布。

  • ShenyuYamlEngine#agentConfig()

ShenyuYamlEngine提供了如何自定义加载yaml格式的文件。

    public static ShenyuAgentConfig agentConfig(final File yamlFile) throws IOException {try (// 读取文件流FileInputStream fileInputStream = new FileInputStream(yamlFile);InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream)) {//指定对应的classConstructor constructor = new Constructor(ShenyuAgentConfig.class);//指定属性的classTypeDescription customTypeDescription = new TypeDescription(AgentPluginConfig.class);customTypeDescription.addPropertyParameters("plugins", Map.class);constructor.addTypeDescription(customTypeDescription);//通过Yaml工具包读取yaml文件 return new Yaml(constructor, new Representer(DUMPER_OPTIONS)).loadAs(inputStreamReader, ShenyuAgentConfig.class);}}

ShenyuAgentConfig是指定的Class类:

public final class ShenyuAgentConfig {// appName 服务名称,默认是 shenyu-agent private String appName = "shenyu-agent";// supports 支持哪些插件private Map<String, List<String>> supports = new LinkedHashMap<>();// plugins 插件的属性信息private Map<String, Map<String, AgentPluginConfig>> plugins = new LinkedHashMap<>();}

AgentPluginConfig是指定插件的Class类:

public final class AgentPluginConfig {// 指定插件的 hostprivate String host;// 指定插件的 portprivate int port;// 指定插件的 passwordprivate String password;// 指定插件的 其他属性props private Properties props;
}

通过配置文件,用户可以指定启用哪个插件,指定插件的属性信息。

3. 加载插件

  • ShenyuAgentPluginLoader#loadAllPlugins()

读取配置文件后,需要根据用户自定义的配置信息,加载指定的插件。由ShenyuAgentPluginLoader来完成。

ShenyuAgentPluginLoader是一个自定义的类加载器,采用单例设计模式。

// 自定义类加载器,继承 ClassLoader
public final class ShenyuAgentPluginLoader extends ClassLoader implements Closeable {// 私有变量private static final ShenyuAgentPluginLoader AGENT_PLUGIN_LOADER = new ShenyuAgentPluginLoader();// 私有构造器private ShenyuAgentPluginLoader() {super(ShenyuAgentPluginLoader.class.getClassLoader());}// 公开静态方法public static ShenyuAgentPluginLoader getInstance() {return AGENT_PLUGIN_LOADER;}/*** 加载所有的插件.*/public void loadAllPlugins() throws IOException {// 1.定位插件路径File[] jarFiles = ShenyuAgentLocator.locatorPlugin().listFiles(file -> file.getName().endsWith(".jar"));if (Objects.isNull(jarFiles)) {return;}// 2.加载插件定义Map<String, ShenyuAgentJoinPoint> pointMap = new HashMap<>();try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {for (File each : jarFiles) {outputStream.reset();JarFile jar = new JarFile(each, true);jars.add(new PluginJar(jar, each));}}loadAgentPluginDefinition(pointMap);Map<String, ShenyuAgentJoinPoint> joinPointMap = ImmutableMap.<String, ShenyuAgentJoinPoint>builder().putAll(pointMap).build();// 3.设置拦截点ShenyuAgentTypeMatcher.getInstance().setJoinPointMap(joinPointMap);}
}

3.1 定位插件路径

  • ShenyuAgentLocator#locatorPlugin()

整个shenyu项目经过maven打包后(执行mvn clean install命令),agent打包目录如下:

插件文件都是jar包形式存在的。

  • conf目录是配置文件的目录位置;
  • plugins目录是各个插件的目录位置。

相应的定位插件路径源码处理逻辑如下:

// 默认插件位于   /plugins 目录下
public static File locatorPlugin() {return new File(String.join("", locatorAgent().getPath(), "/plugins"));
}// 定位shenyu-agent.jar的绝对路径
public static File locatorAgent() {// 找 ShenyuAgentLocator 所在的类路径(包名)String classResourcePath = String.join("", ShenyuAgentLocator.class.getName().replaceAll("\\.", "/"), ".class");// 找到 类 的绝对路径:磁盘路径+类路径URL resource = ClassLoader.getSystemClassLoader().getResource(classResourcePath);assert resource != null;String url = resource.toString();// 是否是以jar包形式存在int existFileInJarIndex = url.indexOf('!');boolean isInJar = existFileInJarIndex > -1;// 从jar包找到路径 或 从资源文件中找路径return isInJar ? getFileInJar(url, existFileInJarIndex) : getFileInResource(url, classResourcePath);}// 从jar包找到路径private static File getFileInJar(final String url, final int fileInJarIndex) {// jar包所在的绝对路径String realUrl = url.substring(url.indexOf("file:"), fileInJarIndex);try {// 以绝对路径创建File对象File agentJarFile = new File(new URL(realUrl).toURI());// 获取父文件return agentJarFile.exists() ? agentJarFile.getParentFile() : null;} catch (final MalformedURLException | URISyntaxException ex) {return null;}}

拿到所有的插件文件后,会去加载插件定义,即拦截点。

3.2 加载拦截点

  • ShenyuAgentPluginLoader#loadAgentPluginDefinition()

拦截点的默认配置是在 conf/tracing-point.yaml文件中,配置格式如下:

pointCuts:- targetClass: org.apache.shenyu.plugin.global.GlobalPlugin  # 拦截目标类points:- type: instanceMethod # 拦截点类型name: execute  # 拦截目标方法handlers:   # 处理器jaeger:   # 用于链路追踪的jaeger插件- org.apache.shenyu.agent.plugin.tracing.jaeger.handler.JaegerGlobalPluginHandleropentelemetry: # 用于链路追踪的opentelemetry插件- org.apache.shenyu.agent.plugin.tracing.opentelemetry.handler.OpenTelemetryGlobalPluginHandlerzipkin:    # 用于链路追踪的zipkin插件- org.apache.shenyu.agent.plugin.tracing.zipkin.handler.ZipkinGlobalPluginHandler// ......

加载拦截点的方式是通过SPI的方式进行加载的,然后再收集这些拦截点。

 private void loadAgentPluginDefinition(final Map<String, ShenyuAgentJoinPoint> pointMap) {SPILoader.loadList(AgentPluginDefinition.class)  // SPI 加载拦截点.forEach(each -> each.collector().forEach(def -> {  // 收集拦截点String classTarget = def.getClassTarget();if (pointMap.containsKey(classTarget)) {ShenyuAgentJoinPoint pluginInterceptorPoint = pointMap.get(classTarget);pluginInterceptorPoint.getConstructorPoints().addAll(def.getConstructorPoints()); // 构造器类型拦截点pluginInterceptorPoint.getInstanceMethodPoints().addAll(def.getInstanceMethodPoints()); // 实例方法类型拦截点pluginInterceptorPoint.getStaticMethodPoints().addAll(def.getStaticMethodPoints()); // 静态方法类型拦截点} else {pointMap.put(classTarget, def);}}));}
  • SPILoader.loadList(AgentPluginDefinition.class)

AgentPluginDefinition是拦截点接口,由@SPI标记:

@SPI // 该接口通过SPI进行加载
public interface AgentPluginDefinition {/*** 收集拦截点 */Collection<ShenyuAgentJoinPoint> collector();
}

TracingAgentPluginDefinition是它的一个实现类,用于定义链路追踪的拦截点:

@Join // SPI的实现类
public final class TracingAgentPluginDefinition extends AbstractAgentPluginDefinition {// 创建拦截点@Overrideprotected Collection<JoinPointBuilder> joinPointBuilder() {// ......}
}public abstract class AbstractAgentPluginDefinition implements AgentPluginDefinition {// 创建拦截点protected abstract Collection<JoinPointBuilder> joinPointBuilder();// 收集拦截点信息@Overridepublic final Collection<ShenyuAgentJoinPoint> collector() {//......}
}

类之间的继承关系如下:

  • AgentPluginDefinition#collector()

    AgentPluginDefinition只是一个接口,定义了收集拦截点的操作方法,具体实现交给了子类。

public abstract class AbstractAgentPluginDefinition implements AgentPluginDefinition {// 子类去实现如何创建拦截点protected abstract Collection<JoinPointBuilder> joinPointBuilder();@Overridepublic final Collection<ShenyuAgentJoinPoint> collector() {// 获取拦截点构建器Collection<JoinPointBuilder> joinPointBuilders = joinPointBuilder();// 创建拦截点对象 ShenyuAgentJoinPointreturn joinPointBuilders.stream().map(JoinPointBuilder::install).collect(Collectors.toList());}
}// 创建拦截点对象 ShenyuAgentJoinPoint
public ShenyuAgentJoinPoint install() {// 四个构造参数分别是:目标对象,构造器拦截点,实例方法拦截点,静态方法拦截点return new ShenyuAgentJoinPoint(classTarget, constructorPoints, instanceMethodPoints, classStaticMethodPoints);
}
  • TracingAgentPluginDefinition#joinPointBuilder()

    创建用于链路追踪的拦截点。

@Join
public final class TracingAgentPluginDefinition extends AbstractAgentPluginDefinition {// 创建拦截点    @Overrideprotected Collection<JoinPointBuilder> joinPointBuilder() {PointCutConfig config = null;try {// 读取默认的拦截点配置文件config = ShenyuYamlEngine.unmarshal(ShenyuAgentLocator.locatorConf("tracing-point.yaml"), PointCutConfig.class);} catch (IOException e) {LOG.error("Exception loader tracing point config is", e);}// 创建拦截点  return JoinPointBuilderFactory.create(config);}
}
  • JoinPointBuilderFactory#create()

    根据指定的配置文件创建拦截点 。

public static Collection<JoinPointBuilder> create(final PointCutConfig config) {//如果没有配置文件或为空,则返回空集合if (Objects.isNull(config) || config.getPointCuts().isEmpty()) {return Collections.emptyList();}return config.getPointCuts().stream() // 获取配置文件中定义的拦截点.filter(pointCut -> StringUtils.isNotEmpty(pointCut.getTargetClass())&& !pointCut.getPoints().isEmpty() && !pointCut.getHandlers().isEmpty()) // 拦截点必须要指定目标类,切入点,处理器.map(pointCut -> {JoinPointBuilder builder = ShenyuAgentJoinPoint.interceptClass(pointCut.getTargetClass()); // 设置需要拦截的目标类Set<String> supports = ShenyuAgentConfigUtils.getSupports(); // 获取当前支持哪些插件List<String> handlers = pointCut.getHandlers().entrySet().stream().filter(entry -> supports.contains(entry.getKey())) // 指定的处理器必须是当前可支持的插件.flatMap(entry -> entry.getValue().stream()).collect(Collectors.toList());String[] instanceMethods = pointCut.getPoints().stream().filter(point -> PointType.INSTANCE_METHOD.getName().equals(point.getType())).map(Point::getName) // 拦截实例方法.toArray(String[]::new);if (instanceMethods.length > 0) {builder.aroundInstanceMethod(ElementMatchers.namedOneOf(instanceMethods)).handlers(handlers).build(); // 为实例方法添加匹配器用于后续运行时动态匹配,并添加对应的处理器}String[] staticMethods = pointCut.getPoints().stream().filter(point -> PointType.STATIC_METHOD.getName().equals(point.getType())).map(Point::getName).toArray(String[]::new); // 拦截静态方法if (staticMethods.length > 0) {builder.aroundStaticMethod(ElementMatchers.namedOneOf(staticMethods)).handlers(handlers).build();// 为静态方法添加匹配器用于后续运行时动态匹配,并添加对应的处理器}String[] constructorPoints = pointCut.getPoints().stream().filter(point -> PointType.CONSTRUCTOR.getName().equals(point.getType())).map(Point::getName).toArray(String[]::new); // 拦截构造器if (constructorPoints.length > 0) {builder.onConstructor(ElementMatchers.namedOneOf(constructorPoints)).handlers(handlers).build();// 为构造器添加匹配器用于后续运行时动态匹配,并添加对应的处理器}return builder;}).collect(Collectors.toList()); // 返回匹配结果}

创建拦截点的主要实现逻辑是:根据配置文件读取配置信息,为指定的目标对象的目标方法添加相应的处理器。处理器有三种:实例方法处理器,静态方法处理器,构造函数处理器。

这里用到了ElementMatchers.namedOneOf() 方法,它表示方法名称在指定的参数中,就可以匹配上这个方法。ElementMatchersbytebuddy中的一个类,在ShenYu中,agent的创建也通过bytebuddy完成的。

后续将收集到的拦截点创建为拦截点对象ShenyuAgentJoinPoint

    public final Collection<ShenyuAgentJoinPoint> collector() {// 获取拦截点Collection<JoinPointBuilder> joinPointBuilders = joinPointBuilder();// 创建拦截点对象return joinPointBuilders.stream().map(JoinPointBuilder::install).collect(Collectors.toList());}

收集完拦截点之后,用Map保存了这些拦截点信息。

// pointMap: Key和Value分别表示目标类,拦截点
private void loadAgentPluginDefinition(final Map<String, ShenyuAgentJoinPoint> pointMap) {SPILoader.loadList(AgentPluginDefinition.class)  // SPI 加载拦截点.forEach(each -> each.collector().forEach(def -> {  // 收集拦截点String classTarget = def.getClassTarget();if (pointMap.containsKey(classTarget)) {ShenyuAgentJoinPoint pluginInterceptorPoint = pointMap.get(classTarget);pluginInterceptorPoint.getConstructorPoints().addAll(def.getConstructorPoints()); // 构造器类型拦截点pluginInterceptorPoint.getInstanceMethodPoints().addAll(def.getInstanceMethodPoints()); // 实例方法类型拦截点pluginInterceptorPoint.getStaticMethodPoints().addAll(def.getStaticMethodPoints()); // 静态方法类型拦截点} else {pointMap.put(classTarget, def); // 将拦截点信息保存到Map中}}));}

3.3 设置拦截点

在加载所有插件的过程中最后一步是设置拦截点。

     public void loadAllPlugins() throws IOException {// 1.定位插件路径// ......// 2.加载插件定义// ......// 3.设置拦截点ShenyuAgentTypeMatcher.getInstance().setJoinPointMap(joinPointMap);}

设置拦截点就是将拦截点集合保存到ShenyuAgentTypeMatcher类中。它实现了ElementMatcher接口,用于自定义匹配逻辑。ElementMatcher也是bytebuddy中的接口。

// 使用单例设计模式
public final class ShenyuAgentTypeMatcher extends ElementMatcher.Junction.AbstractBase<TypeDefinition> {// 创建实例private static final ShenyuAgentTypeMatcher SHENYU_AGENT_TYPE_MATCHER = new ShenyuAgentTypeMatcher();// 拦截点集合private Map<String, ShenyuAgentJoinPoint> joinPointMap;private ShenyuAgentTypeMatcher() {}/*** 获取单例*/public static ShenyuAgentTypeMatcher getInstance() {return SHENYU_AGENT_TYPE_MATCHER;}//自定义匹配逻辑,目标类在拦截点集合中就匹配成功@Overridepublic boolean matches(final TypeDefinition target) {return joinPointMap.containsKey(target.getTypeName());}/*** 设置拦截点集合*/public void setJoinPointMap(final Map<String, ShenyuAgentJoinPoint> joinPointMap) {this.joinPointMap = joinPointMap;}
}

4. 创建 agent

通过创建的agent,用于改变目标类的行为。

 public static void premain(final String arguments, final Instrumentation instrumentation) throws Exception {// 1. 读取配置文件// ......// 2. 加载所有插件// ......// 3. 创建 agentAgentBuilder agentBuilder = new AgentBuilder.Default().with(new ByteBuddy().with(TypeValidation.ENABLED)) // 通过ByteBuddy创建Agent,开启类型校验.ignore(ElementMatchers.isSynthetic()) // 忽略合成类.or(ElementMatchers.nameStartsWith("org.apache.shenyu.agent.")); // 忽略org.apache.shenyu.agent 的类agentBuilder.type(ShenyuAgentTypeMatcher.getInstance())//匹配加载类型,匹配器是ShenyuAgentTypeMatcher.transform(new ShenyuAgentTransformer()) // 匹配成功的,通过ShenyuAgentTransformer改变其行为.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION) //指定重定义策略.with(new TransformListener()) //指定一个监听器,监听运行过程中的事件.installOn(instrumentation); // 将修改应用到instrumentation中// 4. 启动插件// ......}

在创建agent的过程中,需要注意两个点:

  • 是否匹配成功,由ShenyuAgentTypeMatcher决定;
  • 匹配成功的类,通过ShenyuAgentTransformer改变其行为;

接下来我们就来着重分析这两个类。

4.1 定义匹配逻辑

  • ShenyuAgentTypeMatcher#matches()

ShenyuAgentTypeMatcher使用单例的设计模式,实现了ElementMatcher接口,重写了matches()方法。

是否能够匹配成功的逻辑是:如果目标类在拦截点joinPointMap集合中,就匹配成功。


public final class ShenyuAgentTypeMatcher extends ElementMatcher.Junction.AbstractBase<TypeDefinition> {// ......private Map<String, ShenyuAgentJoinPoint> joinPointMap;// 自定义匹配逻辑:如果目标类在拦截点joinPointMap集合中,就匹配成功@Overridepublic boolean matches(final TypeDefinition target) {return joinPointMap.containsKey(target.getTypeName());}}

4.2 改变匹配类的行为

在加载目标类时,如果匹配成功,会通过ShenyuAgentTransformer改变其行为,它实现了Transformer接口,重写了transform()方法,Transformer也是bytebuddy的一个接口。

public final class ShenyuAgentTransformer implements Transformer {//在匹配类中额外定义一个字段,传递上下文信息private static final String EXTRA_DATA = "_$EXTRA_DATA$_";//类型匹配器    private static final ShenyuAgentTypeMatcher MATCHER = ShenyuAgentTypeMatcher.getInstance();//重写transform方法,重新定义匹配类的行为//加载类期间,执行一次@Overridepublic Builder<?> transform(final Builder<?> builder, final TypeDescription typeDescription, final ClassLoader classLoader, final JavaModule module) {//不是匹配的类就跳过if (!MATCHER.containsType(typeDescription)) {return builder;}//为该类新增加一个字段Builder<?> result = builder.defineField(EXTRA_DATA, Object.class, Opcodes.ACC_PRIVATE | Opcodes.ACC_VOLATILE).implement(TargetObject.class).intercept(FieldAccessor.ofField(EXTRA_DATA));// 获取拦截点ShenyuAgentJoinPoint joinPoint = MATCHER.loadShenyuAgentJoinPoint(typeDescription);//拦截构造器result = interceptorConstructorPoint(typeDescription, joinPoint.getConstructorPoints(), result);//拦截静态方法result = interceptorStaticMethodPoint(typeDescription, joinPoint.getStaticMethodPoints(), result);//拦截实例方法result = interceptorInstanceMethodPoint(typeDescription, joinPoint.getInstanceMethodPoints(), result);return result;}// ......
}

transform()方法中,重新定义了匹配类的行为:

  • 新增了字段,是TargetObject的子类,用于传递上下文;
  • 根据指定配置,是否拦截构造器;
  • 根据指定配置,是否拦截静态方法;
  • 根据指定配置,拦截实例方法;
4.2.3 拦截实例方法
  • ShenyuAgentTransformer#interceptorInstanceMethodPoint()

根据切面构建实例方法拦截点,获取Builder对象。

    private Builder<?> interceptorInstanceMethodPoint(final TypeDescription description, final Collection<InstanceMethodPointCut> pointCuts, final Builder<?> builder) {Collection<ShenyuAgentTransformerPoint<?>> points = description.getDeclaredMethods().stream().filter(each -> !(each.isAbstract() || each.isSynthetic())) //过滤抽象方法,合成方法.map(each -> buildInstanceMethodTransformationPoint(pointCuts, each)) //构建实例方法拦截点.filter(Objects::nonNull).collect(Collectors.toList());return getBuilder(description, builder, points); //获取Builder对象}
  • ShenyuAgentTransformer#buildInstanceMethodTransformationPoint()

过滤匹配上的方法,为方法获取对应的handler处理器,最后创建实例方法拦截点对象。

    private ShenyuAgentTransformerPoint<?> buildInstanceMethodTransformationPoint(final Collection<InstanceMethodPointCut> pointCuts, final InDefinedShape methodDescription) {List<InstanceMethodPointCut> points = pointCuts.stream().filter(point -> point.getMatcher().matches(methodDescription)).collect(Collectors.toList()); //过滤能够匹配上的方法if (points.isEmpty()) {return null;}List<InstanceMethodHandler> handlers = points.stream().flatMap(pointCut -> pointCut.getHandlers().stream()).map(handler -> (InstanceMethodHandler) MATCHER.getOrCreateInstance(handler)) //获取对应的handler处理器.filter(Objects::nonNull).collect(Collectors.toList());return new ShenyuAgentTransformerPoint<>(methodDescription, new InstanceMethodInterceptor(handlers));//创建实例方法拦截点对象,创建实例方法拦截器}

方法能否匹配成功:当前方法名称是否是在tracing-point.yaml文件中配置的方法名称;

handler的获取:是根据tracing-point.yaml文件中配置的全限定名去加载对应的类。

  • InstanceMethodInterceptor#intercept()

实例方法拦截器InstanceMethodInterceptor会在运行期间动态处理拦截方法。

public class InstanceMethodInterceptor {// ....../*** 拦截目标对象.** @param target 当前被拦截的目标对象* @param method 目标对象的目标方法* @param args 目标方法的参数* @param callable 目标方法调用* @return 目标方法调用结果* @throws 异常信息*/@RuntimeType //定义运行时的目标方法public Object intercept(@This final Object target, @Origin final Method method, @AllArguments final Object[] args, @SuperCall final Callable<?> callable) throws Exception {//目标方法执行结果Object result = null;//目标对象TargetObject instance = (TargetObject) target;//依次调用handlerfor (InstanceMethodHandler handler : handlerList) {MethodResult methodResult = new MethodResult();// 前置处理逻辑try {handler.before(instance, method, args, methodResult);} catch (final Throwable ex) {LOG.error("Failed to execute the before method of method {} in class {}", method.getName(), target.getClass(), ex);}//调用目标方法try {result = callable.call();} catch (final Throwable ex) {//处理异常try {handler.onThrowing(instance, method, args, ex);} catch (final Throwable ignored) {LOG.error("Failed to execute the error handler of method {} in class {}", method.getName(), target.getClass(), ex);throw ex;}} finally {//后置处理逻辑try {result = handler.after(instance, method, args, methodResult, result);} catch (final Throwable ex) {LOG.error("Failed to execute the after method of method {} in class {}", method.getName(), target.getClass(), ex);}}}//返回目标方法调用结果return result;}
}

实例方法拦截器在目标方法调用前,增加了前置处理逻辑,后置处理逻辑,以及异常处理逻辑。

这里用到了Byte Buddy的几个注解:

  • @RuntimeType: 定义运行时的目标方法,提示ByteBuddy禁用严格的类型检查;
  • @This:当前被拦截的、动态生成的实例对象;
  • @Origin:原有方法;
  • @AllArguments:获取所有入参;
  • @SuperCall:用于调用父类版本的方法。

实例方法处理器InstanceMethodHandler只是定义了三个接口,具体实现逻辑由具体插件去处理。

public interface InstanceMethodHandler {// 前置处理逻辑default void before(final TargetObject target, final Method method, final Object[] args, final MethodResult result) {}// 后置处理逻辑default Object after(final TargetObject target, final Method method, final Object[] args, final MethodResult methodResult, final Object result) {return result;}// 异常处理逻辑default void onThrowing(final TargetObject target, final Method method, final Object[] args, final Throwable throwable) {}
}
  • ShenyuAgentTransformer#getBuilder()

    AgentBuilder获取时,为指定的目标方法指定对应的拦截器,在原有方法上添加新的逻辑,改变原有方法行为。

    private static Builder<?> getBuilder(final TypeDescription description, final Builder<?> builder, final Collection<ShenyuAgentTransformerPoint<?>> points) {final Builder<?>[] result = {builder};points.forEach(point -> {try {result[0] = builder.method(ElementMatchers.is(point.getDescription()))//指定目标方法.intercept(MethodDelegation.withDefaultConfiguration().to(point.getInterceptor()));//指定拦截器// CHECKSTYLE:OFF} catch (final Throwable ex) {// CHECKSTYLE:ONLOG.error("Failed to load handler class: {}", description.getTypeName(), ex);}});return result[0];}

通过以上的处理逻辑,就可以实现无侵入拦截实例方法了。

拦截方法intercept()的处理逻辑是:

  • 依次处理每个handler
  • 调用handler的前置方法;
  • 调用目标方法;
  • 如果目标方法有异常,则调用handler的异常处理方法;
  • 调用handler的后置方法。

接下来看看拦截静态方法。

4.2.3 拦截静态方法
  • ShenyuAgentTransformer#interceptorStaticMethodPoint()

过滤出静态方法,然后为静态方法构建静态方法拦截点。

    private Builder<?> interceptorStaticMethodPoint(final TypeDescription description, final Collection<StaticMethodPointCut> pointCuts, final Builder<?> builder) {Collection<ShenyuAgentTransformerPoint<?>> points = description.getDeclaredMethods().stream().filter(each -> each.isStatic() && !(each.isAbstract() || each.isSynthetic())) // 当前方法是静态方法,不是抽象方法,不是合成方法.map(methodDescription -> buildStaticMethodTransformationPoint(pointCuts, methodDescription)) // 构建静态方法拦截点.filter(Objects::nonNull).collect(Collectors.toList());return getBuilder(description, builder, points);}
  • ShenyuAgentTransformer#buildStaticMethodTransformationPoint()

根据配置文件进行过滤,判断当前的静态方法是否需要拦截。然后获取对应的处理器,最后构建静态方法拦截器对象。

    private ShenyuAgentTransformerPoint<?> buildStaticMethodTransformationPoint(final Collection<StaticMethodPointCut> pointCuts, final InDefinedShape methodDescription) {List<StaticMethodPointCut> staticMethodPoints = pointCuts.stream().filter(point -> point.getMatcher().matches(methodDescription)).collect(Collectors.toList()); //根据配置文件进行过滤,判断当前的静态方法是否需要拦截if (staticMethodPoints.isEmpty()) { // 如果没有配置,就直接返回了return null;}List<StaticMethodHandler> handlers = staticMethodPoints.stream().flatMap(pointCut -> pointCut.getHandlers().stream()).map(handler -> (StaticMethodHandler) MATCHER.getOrCreateInstance(handler)) //获取对应的处理器.filter(Objects::nonNull).collect(Collectors.toList());return new ShenyuAgentTransformerPoint<>(methodDescription, new StaticMethodInterceptor(handlers)); //构建静态方法拦截器对象}
  • StaticMethodInterceptor#intercept()

在运行时,会拦截目标方法,执行拦截器的处理逻辑 。


public class StaticMethodInterceptor {//......  /*** 拦截目标方法.*/@RuntimeTypepublic Object intercept(@Origin final Class<?> klass, @Origin final Method method, @AllArguments final Object[] args, @SuperCall final Callable<?> callable) throws Exception {Object result = null;// handler循环处理for (StaticMethodHandler handler : handlerList) {MethodResult methodResult = new MethodResult();try {//前置方法handler.before(klass, method, args, new MethodResult());} catch (final Throwable ex) {LOG.error("Failed to execute the before method of method {} in class {}", method.getName(), klass, ex);}try {// 调用当前方法// 目标方法是不是应该只会被调用一次?result = callable.call();} catch (final Throwable ex) {try {//异常逻辑处理handler.onThrowing(klass, method, args, ex);} catch (final Throwable ignored) {LOG.error("Failed to execute the error handler of method {} in class {}", method.getName(), klass, ex);throw ex;}} finally {try {// 后置方法handler.after(klass, method, args, methodResult);} catch (final Throwable ex) {LOG.error("Failed to execute the after method of method {} in class {}", method.getName(), klass, ex);}}}//返回方法调用结果return result;}
}

拦截方法intercept()的处理逻辑是:

  • 依次处理每个handler
  • 调用handler的前置方法;
  • 调用目标方法;
  • 如果目标方法有异常,则调用handler的异常处理方法;
  • 调用handler的后置方法。

最后再看看如何拦截构造器。

4.2.3 拦截构造器
  • ShenyuAgentTransformer#interceptorConstructorPoint()

过滤出构造器,然后构建构造器的拦截点,最后创建builder对象,为构造方法添加拦截器。

 private Builder<?> interceptorConstructorPoint(final TypeDescription description, final Collection<ConstructorPointCut> constructorPoints, final Builder<?> builder) {Collection<ShenyuAgentTransformerPoint<? extends ConstructorInterceptor>> constructorAdviceComposePoints = description.getDeclaredMethods().stream().filter(MethodDescription::isConstructor) //过滤出构造器.map(each -> buildConstructorTransformerPoint(constructorPoints, each))//构建构造器的拦截点.filter(Objects::nonNull).collect(Collectors.toList());final Builder<?>[] result = {builder};// 创建builder对象,为构造方法添加拦截器constructorAdviceComposePoints.forEach(point -> {try {result[0] = builder.constructor(ElementMatchers.is(point.getDescription())).intercept(SuperMethodCall.INSTANCE.andThen(MethodDelegation.withDefaultConfiguration().to(point.getInterceptor())));//先调用父类构造器,然后添加拦截器// CHECKSTYLE:OFF} catch (final Throwable ex) {// CHECKSTYLE:ONLOG.error("Failed to load handler class: {}", description.getTypeName(), ex);}});return result[0];}
  • ShenyuAgentTransformer#buildConstructorTransformerPoint()

先获取到构造器拦截点,然后为拦截点创建handler实例对象,最后创建构造器拦截器对象。

private ShenyuAgentTransformerPoint<? extends ConstructorInterceptor> buildConstructorTransformerPoint(final Collection<ConstructorPointCut> constructorPoints, final InDefinedShape methodDescription) {//获取构造器拦截点List<ConstructorPointCut> constructorPointCutList = constructorPoints.stream().filter(each -> each.getMatcher().matches(methodDescription)).collect(Collectors.toList());if (constructorPointCutList.isEmpty()) {return null;}List<ConstructorHandler> handlers = constructorPointCutList.stream().flatMap(pointCut -> pointCut.getHandlers().stream()).map(handler -> (ConstructorHandler) MATCHER.getOrCreateInstance(handler)) //创建拦截点的handler实例对象.filter(Objects::nonNull).collect(Collectors.toList());return new ShenyuAgentTransformerPoint<>(methodDescription, new ConstructorInterceptor(handlers));//创建构造器拦截器}
  • ConstructorInterceptor#intercept()

构造器拦截器:在调用目标方法的构造器之前,执行每个handler的处理逻辑。


public class ConstructorInterceptor {//....../*** 拦截方法.*/@RuntimeTypepublic void intercept(@This final TargetObject target, @AllArguments final Object[] args) {for (ConstructorHandler handler : handlerList) {try {// handler处理逻辑handler.onConstructor(target, args);} catch (final Throwable throwable) {LOG.error("Constructor advice execution error. class: {}", target.getClass().getTypeName(), throwable);}}}
}

分析到此,我们分析完了创建agent的整个过程:

  • 根据配置文件,定义匹配逻辑ShenyuAgentTypeMatcher
  • 定义ShenyuAgentTransformer对象,改变匹配类的行为;
  • 通过InstanceMethodInterceptor拦截实例对象方法;
  • 通过StaticMethodInterceptor拦截静态方法;
  • 通过ConstructorInterceptor拦截构造器。

这里没有提到每个handler的处理逻辑,是因为handler的实现逻辑由每个插件自定义。比如,当前实例方法拦截器InstanceMethodHandler的实现类就有jaegeropentelemetryzipkin

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KFJdWGlT-1647258166340)(https://qiniu.midnight2104.com/20220216/instance_method_handler.png)]

5. 启动插件

创建完 agent之后,启动各个插件。

 public static void premain(final String arguments, final Instrumentation instrumentation) throws Exception {// 1. 读取配置文件// ......// 2. 加载所有插件// ......// 3. 创建 agent// 4. 启动插件PluginLifecycleManager lifecycleManager = new PluginLifecycleManager();lifecycleManager.startup(ShenyuAgentConfigUtils.getPluginConfigMap());//添加hook函数用于关闭插件Runtime.getRuntime().addShutdownHook(new Thread(lifecycleManager::close));}
  • PluginLifecycleManager

    PluginLifecycleManager负责插件的生命周期管理,用于启动插件和关闭插件。

    插件的启动,必须要在配置文件中指定。


public class PluginLifecycleManager {//......      /*** 启动插件.*/public void startup(final Map<String, AgentPluginConfig> configMap) {//从配置文件中获取支持的插件名称Set<String> support = ShenyuAgentConfigUtils.getSupports();configMap.entrySet().stream().filter(entry -> support.contains(entry.getKey())) //包含在配置文件中.forEach(entry -> Optional.ofNullable(SPILoader.load(AgentPluginBootService.class, entry.getKey())) //通过SPI加载插件启动类.ifPresent(bootService -> {try {LOG.info("start shenyu plugin: {}", entry.getKey());bootService.start(entry.getValue());  // 启动插件:执行插件的具体启动逻辑} catch (final Throwable ex) {LOG.error("Failed to start shenyu plugin", ex);}}));}/*** 关闭插件*/public void close() {//通过SPI加载插件启动类SPILoader.loadList(AgentPluginBootService.class).forEach(each -> {try {each.close(); // 关闭插件:执行插件的具体关闭逻辑} catch (final Throwable ex) {LOG.error("Failed to close shenyu agent plugin", ex);}});}
}

插件的启动和关闭也是有每个插件具体去实现的,然后通过SPI去加载。

6. 总结

  • shenyu-agent模块的实现主要是通过Byte Buddy工具包;
  • 在配置文件shenyu-agent.yaml中,指定插件信息;
  • 插件加载过程通过SPI完成;
  • 拦截点通过配置文件指定,设计灵活;
  • 插件接口定义和实现分开,支持多种插件类型。

Apache ShenYu源码阅读系列-Agent模块源码分析相关推荐

  1. 【Dubbo源码阅读系列】之远程服务调用(上)

    今天打算来讲一讲 Dubbo 服务远程调用.笔者在开始看 Dubbo 远程服务相关源码的时候,看的有点迷糊.后来慢慢明白 Dubbo 远程服务的调用的本质就是动态代理模式的一种实现.本地消费者无须知道 ...

  2. TiDB 源码阅读系列文章(六)Select 语句概览

    在先前的 TiDB 源码阅读系列文章(四) 中,我们介绍了 Insert 语句,想必大家已经了解了 TiDB 是如何写入数据,本篇文章介绍一下 Select 语句是如何执行.相比 Insert,Sel ...

  3. TiDB 源码阅读系列文章(十九)tikv-client(下)

    上篇文章 中,我们介绍了数据读写过程中 tikv-client 需要解决的几个具体问题,本文将继续介绍 tikv-client 里的两个主要的模块--负责处理分布式计算的 copIterator 和执 ...

  4. SpringMVC源码阅读系列汇总

    1.前言 1.1 导入 SpringMVC是基于Servlet和Spring框架设计的Web框架,做JavaWeb的同学应该都知道 本文基于Spring4.3.7源码分析,(不要被图片欺骗了,手动滑稽 ...

  5. TiDB 源码阅读系列文章(十六)INSERT 语句详解

    在之前的一篇文章 <TiDB 源码阅读系列文章(四)INSERT 语句概览> 中,我们已经介绍了 INSERT 语句的大体流程.为什么需要为 INSERT 单独再写一篇?因为在 TiDB ...

  6. 【Dubbo源码阅读系列】服务暴露之本地暴露

    在上一篇文章中我们介绍 Dubbo 自定义标签解析相关内容,其中我们自定义的 XML 标签 <dubbo:service /> 会被解析为 ServiceBean 对象(传送门:Dubbo ...

  7. TiDB 源码阅读系列文章(六)Select 语句概览 1

    在先前的 TiDB 源码阅读系列文章(四) 中,我们介绍了 Insert 语句,想必大家已经了解了 TiDB 是如何写入数据,本篇文章介绍一下 Select 语句是如何执行.相比 Insert,Sel ...

  8. FreeSWITCH 1.10 源码阅读(3)-sofia 模块原理及其呼入处理流程

    文章目录 1. 前言 2. 源码分析 2.1 sofia 模块的加载 2.2 呼入的处理流程 1. 前言 SIP(Session Initiation Protocol) 是应用层的信令控制协议,有许 ...

  9. TiDB 源码阅读系列文章(五)TiDB SQL Parser 的实现

    本文为 TiDB 源码阅读系列文章的第五篇,主要对 SQL Parser 功能的实现进行了讲解,内容来自社区小伙伴--马震(GitHub ID:mz1999 )的投稿. TiDB 源码阅读系列文章的撰 ...

  10. TiDB 源码阅读系列文章(十五)Sort Merge Join

    2019独角兽企业重金招聘Python工程师标准>>> 什么是 Sort Merge Join 在开始阅读源码之前, 我们来看看什么是 Sort Merge Join (SMJ),定 ...

最新文章

  1. 速来!亚马逊云科技AI盛会开源专场吹响集结号
  2. 跟我学大数据分析:多维度数据分析报告
  3. jQuery——封装form表单的数据为json对象
  4. iOS 事件传递响应链
  5. swagger2 注解说明文档
  6. promise then返回值
  7. Linux这么多命令怎么记住?
  8. 应用FileInputStream类,编写应用程序,从磁盘上读取一个Java程序,并将源程序代码显示在屏幕上
  9. python datetime.datetime.now_python时间日期datetime模块
  10. 串口监视软件_力控监控组态软件与西门子S7200
  11. java dht 爬虫_python开发的 dht网络爬虫
  12. docker swarm 部署 sentry9.1.2
  13. Xposed框架安装步骤
  14. 日期格式 Wed Oct 16 00:00:00 CEST 2020 转换
  15. python 中的 Fraction和GCD求最大公约数
  16. PKI、KDC、DH、RSA都是些什么
  17. oneDNS解决google等登陆问题
  18. 涂鸦Zigbee SDK开发系列教程——2.环境搭建
  19. 全网: Mac安装ansible
  20. Dijkstra算法(单源最短路径)

热门文章

  1. Win10 + Ubuntu 安装教程(痛苦踩坑)
  2. java base64图片计算图片大小
  3. 2159: H.ly的小迷弟
  4. 微信公众号迁移公证需要哪些材料?账号迁移流程来了
  5. 测试微信好友是否删除软件,如何测试微信好友是否删了你?
  6. python在视频上方加字_如何用python 在视频上添加自己的logo
  7. 计算机网络面经八股文
  8. Java之HashMap系列--保证线程安全的方法
  9. 【office】无法卸载office?
  10. 监控服务器系统密码忘了怎么办,监控服务器系统配置密码