Apache ShenYu 是一个异步的,高性能的,跨语言的,响应式的 API 网关。
在ShenYu网关中,Apache ShenYu 利用 Java Agent 和 字节码增强 技术实现了无痕埋点,使得用户无需引入依赖即可接入第三方可观测性系统,获取 Traces、Metrics 和 Logging 。
本文基于shenyu-2.4.2版本进行源码分析,官网的介绍请参考 可观测性 。
具体而言,就是shenyu-agent模块,它基于 Java Agent 机制,通过ByteBuddy字节码增强库,在类加载时增强对象,属于静态代理。
在分析源码之前,介绍下AOP相关的术语,便于后续的理解:
Byte Buddy是一个代码生成和操作库,在Java应用程序的运行期间创建和修改Java类。可以利用它创建任何类,不像JDK动态代理那样强制实现一个接口。此外,Byte Buddy提供了方便的 API,用于手动、使用Java代理或在构建期间改变类。
1. premain 入口
premain()函数是javaagent 的入口函数,在 ShenYu由 ShenyuAgentBootstrap 提供并实现整个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. 创建 agent AgentBuilder 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函数的核心逻辑,就是上面的四步操作:
读取配置文件;
加载所有插件;
创建 agent;
启动插件。
接下来的源码分析就依次分析这四个操作。
2. 读取配置文件
配置文件的处理由 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.yaml File 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 - zipkin metrics: # 统计度量插件 - logging: # 日志信息插件 - plugins: # 每个插件的具体配置信息 tracing: # 链路追踪的插件 jaeger: # jaeger的相关配置 host: "localhost" port: 5775 props: SERVICE_NAME: "shenyu-agent" JAEGER_SAMPLER_TYPE: "const" JAEGER_SAMPLER_PARAM: "1" opentelemetry: # opentelemetry的相关配置 props: otel.traces.exporter: jaeger #zipkin #otlp otel.resource.attributes: "service.name=shenyu-agent" otel.exporter.jaeger.endpoint: "http://localhost:14250/api/traces" zipkin: # zipkin的相关配置 host: "localhost" port: 9411 props: SERVICE_NAME: "shenyu-agent" URL_VERSION: "/api/v2/spans" SAMPLER_TYPE: "const" SAMPLER_PARAM: "1" metrics: # 统计度量插件 prometheus: # prometheus的相关配置 host: "localhost" port: 8081 props: logging: # 日志信息插件 elasticSearch: # es的相关配置 host: "localhost" port: 8082 props: kafka: # kafka的相关配置 host: "localhost" port: 8082 props:
复制代码
需要开启哪个插件,就在supports中指定,然后再plugins指定插件的配置信息。
到目前为止,Apache ShenYu 发布的最新版本是2.4.2 版本,可以支持tracing的插件有jaeger、opentelemetry和zipkin,metrics和logging将在后续的版本中陆续发布。
ShenyuYamlEngine提供了如何自定义加载yaml格式的文件。
public static ShenyuAgentConfig agentConfig(final File yamlFile) throws IOException { try ( // 读取文件流 FileInputStream fileInputStream = new FileInputStream(yamlFile); InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream) ) { //指定对应的class Constructor constructor = new Constructor(ShenyuAgentConfig.class); //指定属性的class TypeDescription 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 { // 指定插件的 host private String host; // 指定插件的 port private int port; // 指定插件的 password private String password; // 指定插件的 其他属性props private Properties props;}
复制代码
通过配置文件,用户可以指定启用哪个插件,指定插件的属性信息。
3. 加载插件
读取配置文件后,需要根据用户自定义的配置信息,加载指定的插件。由ShenyuAgentPluginLoader来完成。
ShenyuAgentPluginLoader是一个自定义的类加载器,采用单例设计模式。
// 自定义类加载器,继承 ClassLoaderpublic 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 定位插件路径
整个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 加载拦截点
拦截点的默认配置是在 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.JaegerGlobalPluginHandler opentelemetry: # 用于链路追踪的opentelemetry插件 - org.apache.shenyu.agent.plugin.tracing.opentelemetry.handler.OpenTelemetryGlobalPluginHandler zipkin: # 用于链路追踪的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); } })); }
复制代码
AgentPluginDefinition是拦截点接口,由@SPI标记:
@SPI // 该接口通过SPI进行加载public interface AgentPluginDefinition { /** * 收集拦截点 */ Collection<ShenyuAgentJoinPoint> collector();}
复制代码
TracingAgentPluginDefinition是它的一个实现类,用于定义链路追踪的拦截点:
@Join // SPI的实现类public final class TracingAgentPluginDefinition extends AbstractAgentPluginDefinition { // 创建拦截点 @Override protected Collection<JoinPointBuilder> joinPointBuilder() { // ...... }}
public abstract class AbstractAgentPluginDefinition implements AgentPluginDefinition { // 创建拦截点 protected abstract Collection<JoinPointBuilder> joinPointBuilder(); // 收集拦截点信息 @Override public final Collection<ShenyuAgentJoinPoint> collector() { //...... }}
复制代码
类之间的继承关系如下:
public abstract class AbstractAgentPluginDefinition implements AgentPluginDefinition { // 子类去实现如何创建拦截点 protected abstract Collection<JoinPointBuilder> joinPointBuilder();
@Override public final Collection<ShenyuAgentJoinPoint> collector() { // 获取拦截点构建器 Collection<JoinPointBuilder> joinPointBuilders = joinPointBuilder(); // 创建拦截点对象 ShenyuAgentJoinPoint return joinPointBuilders.stream().map(JoinPointBuilder::install).collect(Collectors.toList()); }}
// 创建拦截点对象 ShenyuAgentJoinPointpublic ShenyuAgentJoinPoint install() { // 四个构造参数分别是:目标对象,构造器拦截点,实例方法拦截点,静态方法拦截点 return new ShenyuAgentJoinPoint(classTarget, constructorPoints, instanceMethodPoints, classStaticMethodPoints);}
复制代码
@Joinpublic final class TracingAgentPluginDefinition extends AbstractAgentPluginDefinition { // 创建拦截点 @Override protected 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); }}
复制代码
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() 方法,它表示方法名称在指定的参数中,就可以匹配上这个方法。ElementMatchers是bytebuddy中的一个类,在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; } //自定义匹配逻辑,目标类在拦截点集合中就匹配成功 @Override public 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. 创建 agent AgentBuilder 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 的过程中,需要注意两个点:
接下来我们就来着重分析这两个类。
4.1 定义匹配逻辑
ShenyuAgentTypeMatcher使用单例的设计模式,实现了ElementMatcher接口,重写了matches()方法。
是否能够匹配成功的逻辑是:如果目标类在拦截点joinPointMap集合中,就匹配成功。
public final class ShenyuAgentTypeMatcher extends ElementMatcher.Junction.AbstractBase<TypeDefinition> { // ...... private Map<String, ShenyuAgentJoinPoint> joinPointMap;
// 自定义匹配逻辑:如果目标类在拦截点joinPointMap集合中,就匹配成功 @Override public 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方法,重新定义匹配类的行为 //加载类期间,执行一次 @Override public 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()方法中,重新定义了匹配类的行为:
4.2.3 拦截实例方法
根据切面构建实例方法拦截点,获取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对象 }
复制代码
过滤匹配上的方法,为方法获取对应的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会在运行期间动态处理拦截方法。
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; //依次调用handler for (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的几个注解:
实例方法处理器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) { }}
复制代码
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:ON LOG.error("Failed to load handler class: {}", description.getTypeName(), ex); } }); return result[0]; }
复制代码
通过以上的处理逻辑,就可以实现无侵入拦截实例方法了。
拦截方法intercept()的处理逻辑是:
接下来看看拦截静态方法。
4.2.3 拦截静态方法
过滤出静态方法,然后为静态方法构建静态方法拦截点。
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); }
复制代码
根据配置文件进行过滤,判断当前的静态方法是否需要拦截。然后获取对应的处理器,最后构建静态方法拦截器对象。
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)); //构建静态方法拦截器对象 }
复制代码
在运行时,会拦截目标方法,执行拦截器的处理逻辑 。
public class StaticMethodInterceptor { //...... /** * 拦截目标方法. */ @RuntimeType public 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()的处理逻辑是:
最后再看看如何拦截构造器。
4.2.3 拦截构造器
过滤出构造器,然后构建构造器的拦截点,最后创建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:ON LOG.error("Failed to load handler class: {}", description.getTypeName(), ex); } }); return result[0]; }
复制代码
先获取到构造器拦截点,然后为拦截点创建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));//创建构造器拦截器 }
复制代码
构造器拦截器:在调用目标方法的构造器之前,执行每个handler的处理逻辑。
public class ConstructorInterceptor { //...... /** * 拦截方法. */ @RuntimeType public 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的实现类就有jaeger,opentelemetry和zipkin。
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)); }
复制代码
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. 总结
评论