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
是一个自定义的类加载器,采用单例设计模式。
// 自定义类加载器,继承 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 定位插件路径
整个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());
}
}
// 创建拦截点对象 ShenyuAgentJoinPoint
public ShenyuAgentJoinPoint install() {
// 四个构造参数分别是:目标对象,构造器拦截点,实例方法拦截点,静态方法拦截点
return new ShenyuAgentJoinPoint(classTarget, constructorPoints, instanceMethodPoints, classStaticMethodPoints);
}
复制代码
@Join
public 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. 总结
评论