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 代理或在构建期间改变类。
- 提供了非常方便的API接口,与强大的类,方法等匹配功能;
- 开箱即用,零学习成本,屏蔽了底层操作字节码技术;
- 强大的开放定制性功能,可以为任何实现的方法自定义字节码;
- 最少运行时生成代码原则,性能高效;
1. premain入口
premain()函数 是javaagent 的入口函数,在 ShenYu 由 ShenyuAgentBootstrap 提供并实现整个agent 的逻辑。
public class ShenyuAgentBootstrap {
public static void premain(final String arguments, final Instrumentation instrumentation) throws Exception {
ShenyuAgentConfigUtils.setConfig(ShenyuAgentConfigLoader.load());
ShenyuAgentPluginLoader.getInstance().loadAllPlugins();
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);
PluginLifecycleManager lifecycleManager = new PluginLifecycleManager();
lifecycleManager.startup(ShenyuAgentConfigUtils.getPluginConfigMap());
Runtime.getRuntime().addShutdownHook(new Thread(lifecycleManager::close));
}
}
premain函数 的核心逻辑,就是上面的四步操作:
-
- 读取配置文件;
-
- 加载所有插件;
-
- 创建 agent;
-
- 启动插件。
接下来的源码分析就依次分析这四个操作。
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);
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#agentConfig()
ShenyuYamlEngine 提供了如何自定义加载yaml 格式的文件。
public static ShenyuAgentConfig agentConfig(final File yamlFile) throws IOException {
try (
FileInputStream fileInputStream = new FileInputStream(yamlFile);
InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream)
) {
Constructor constructor = new Constructor(ShenyuAgentConfig.class);
TypeDescription customTypeDescription = new TypeDescription(AgentPluginConfig.class);
customTypeDescription.addPropertyParameters("plugins", Map.class);
constructor.addTypeDescription(customTypeDescription);
return new Yaml(constructor, new Representer(DUMPER_OPTIONS)).loadAs(inputStreamReader, ShenyuAgentConfig.class);
}
}
ShenyuAgentConfig 是指定的Class类:
public final class ShenyuAgentConfig {
private String appName = "shenyu-agent";
private Map<String, List<String>> supports = new LinkedHashMap<>();
private Map<String, Map<String, AgentPluginConfig>> plugins = new LinkedHashMap<>();
}
AgentPluginConfig 是指定插件的Class类:
public final class AgentPluginConfig {
private String host;
private int port;
private String password;
private Properties props;
}
通过配置文件,用户可以指定启用哪个插件,指定插件的属性信息。
3. 加载插件
- ShenyuAgentPluginLoader#loadAllPlugins()
读取配置文件后,需要根据用户自定义的配置信息,加载指定的插件。由ShenyuAgentPluginLoader 来完成。
ShenyuAgentPluginLoader 是一个自定义的类加载器,采用单例设计模式。
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 {
File[] jarFiles = ShenyuAgentLocator.locatorPlugin().listFiles(file -> file.getName().endsWith(".jar"));
if (Objects.isNull(jarFiles)) {
return;
}
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();
ShenyuAgentTypeMatcher.getInstance().setJoinPointMap(joinPointMap);
}
}
3.1 定位插件路径
- ShenyuAgentLocator#locatorPlugin()
整个shenyu 项目经过maven 打包后(执行mvn clean install 命令),agent 打包目录如下:
插件文件都是jar 包形式存在的。
conf 目录是配置文件的目录位置;plugins 目录是各个插件的目录位置。
相应的定位插件路径源码处理逻辑如下:
public static File locatorPlugin() {
return new File(String.join("", locatorAgent().getPath(), "/plugins"));
}
public static File locatorAgent() {
String classResourcePath = String.join("", ShenyuAgentLocator.class.getName().replaceAll("\\.", "/"), ".class");
URL resource = ClassLoader.getSystemClassLoader().getResource(classResourcePath);
assert resource != null;
String url = resource.toString();
int existFileInJarIndex = url.indexOf('!');
boolean isInJar = existFileInJarIndex > -1;
return isInJar ? getFileInJar(url, existFileInJarIndex) : getFileInResource(url, classResourcePath);
}
private static File getFileInJar(final String url, final int fileInJarIndex) {
String realUrl = url.substring(url.indexOf("file:"), fileInJarIndex);
try {
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:
- org.apache.shenyu.agent.plugin.tracing.jaeger.handler.JaegerGlobalPluginHandler
opentelemetry:
- org.apache.shenyu.agent.plugin.tracing.opentelemetry.handler.OpenTelemetryGlobalPluginHandler
zipkin:
- org.apache.shenyu.agent.plugin.tracing.zipkin.handler.ZipkinGlobalPluginHandler
// ......
加载拦截点的方式是通过SPI 的方式进行加载的,然后再收集这些拦截点。
private void loadAgentPluginDefinition(final Map<String, ShenyuAgentJoinPoint> pointMap) {
SPILoader.loadList(AgentPluginDefinition.class)
.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
public interface AgentPluginDefinition {
Collection<ShenyuAgentJoinPoint> collector();
}
TracingAgentPluginDefinition 是它的一个实现类,用于定义链路追踪的拦截点:
@Join
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();
return joinPointBuilders.stream().map(JoinPointBuilder::install).collect(Collectors.toList());
}
}
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 保存了这些拦截点信息。
private void loadAgentPluginDefinition(final Map<String, ShenyuAgentJoinPoint> pointMap) {
SPILoader.loadList(AgentPluginDefinition.class)
.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);
}
}));
}
3.3 设置拦截点
在加载所有插件的过程中最后一步是设置拦截点。
public void loadAllPlugins() throws IOException {
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 {
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);
}
在创建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;
@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();
@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() 方法中,重新定义了匹配类的行为:
- 新增了字段,是
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);
}
- 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))
.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 {
@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;
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 的几个注解:
@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) {
}
}
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()));
} catch (final Throwable ex) {
LOG.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 {
@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;
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};
constructorAdviceComposePoints.forEach(point -> {
try {
result[0] = builder.constructor(ElementMatchers.is(point.getDescription()))
.intercept(SuperMethodCall.INSTANCE.andThen(MethodDelegation.withDefaultConfiguration()
.to(point.getInterceptor())));
} catch (final Throwable ex) {
LOG.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))
.filter(Objects::nonNull)
.collect(Collectors.toList());
return new ShenyuAgentTransformerPoint<>(methodDescription, new ConstructorInterceptor(handlers));
}
- ConstructorInterceptor#intercept()
构造器拦截器:在调用目标方法的构造器之前,执行每个handler 的处理逻辑。
public class ConstructorInterceptor {
@RuntimeType
public void intercept(@This final TargetObject target, @AllArguments final Object[] args) {
for (ConstructorHandler handler : handlerList) {
try {
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 。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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 {
PluginLifecycleManager lifecycleManager = new PluginLifecycleManager();
lifecycleManager.startup(ShenyuAgentConfigUtils.getPluginConfigMap());
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()))
.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() {
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 完成; - 拦截点通过配置文件指定,设计灵活;
- 插件接口定义和实现分开,支持多种插件类型。
|