Sermant源码(一)agent premain挂载
前言
本章基于Sermant2.0.0版本分析sermant-agent通过premain方式挂载。
- premain主流程;
- 类加载流程;
- 运行时被增强方法执行流程;
- sermant类加载器设计;
一、AgentLauncher
AgentLauncher是agent的启动入口由AppClassLoader加载。
AgentLauncher#launchAgent:
入参isDynamic,false-premain应用启动挂载,true-agentmain增强应用运行挂载,后续主要分析premain方式。
- 将god包加入BootStrap顶层类加载器,这个很重要;
- 根据agent参数和bootstrap.properties(优先级前者大于后者),确定artifact、serviceName、appName等属性;
- installAgent主方法;
private static void launchAgent(String agentArgs, Instrumentation instrumentation, boolean isDynamic) {
try {
final Map<String, String> agentArgsMap = AgentArgsResolver.resolveAgentArgs(agentArgs);
// god包加入Bootstrap顶层类加载器
installGodLibs(instrumentation);
// 读取bootstrap.properties artifact、appName、appType、serviceName,注入bootArgsMap
String agentPath = agentArgsMap.get(BootConstant.AGENT_PATH_KEY);
Map<String, Object> bootArgsMap = new HashMap<>(agentArgsMap);
BootArgsBuilder.build(bootArgsMap, agentPath);
String artifact = (String) bootArgsMap.get(BootConstant.ARTIFACT_NAME_KEY);
// 主方法入口
installAgent(instrumentation, isDynamic, artifact, bootArgsMap, agentPath);
// 执行命令(忽略)
executeCommand(artifact, agentArgsMap);
} catch (Exception exception) {
LOGGER.log(Level.SEVERE, "Loading sermant agent failed: " + exception.getMessage());
}
}
AgentLauncher#installAgent:比较关键
- SermantManager(god) 校验artifact是否被安装过;
- SermantManager创建一个SermantClassLoader(god) ;
- SermantClassLoader加载AgentCoreEntrance(core) ,AgentCoreEntrance真实执行启动逻辑;
private static void installAgent(Instrumentation instrumentation, boolean isDynamic, String artifact,
Map<String, Object> argsMap, String agentPath) {
// SermantManager由god提供,Bootstrap类加载器加载,控制一个artifact只能安装一次
if (!SermantManager.checkSermantStatus(artifact)) {
// SermantClassLoader由god提供
// 每个artifact创建一个SermantClassLoader,对于premain挂载,只需要考虑一个SermantClassLoader
// SermantClassLoader加载core包(包含byte-buddy),父类加载器是AppClassLoader
SermantClassLoader sermantClassLoader = SermantManager.createSermant(artifact, getCoreLibUrls(
agentPath));
// SermantClassLoader加载AgentCoreEntrance启动
sermantClassLoader.loadClass(BootConstant.AGENT_CORE_ENTRANCE_CLASS)
.getDeclaredMethod(BootConstant.AGENT_INSTALL_METHOD, String.class, Map.class,
Instrumentation.class, boolean.class)
.invoke(null, artifact, argsMap, instrumentation, isDynamic);
LOGGER.log(Level.INFO, "Load sermant done, artifact is: " + artifact);
SermantManager.updateSermantStatus(artifact, true);
} else {
LOGGER.log(Level.INFO, "Sermant for artifact is running, artifact is: " + artifact);
}
}
二、SermantClassLoader
sermant-agent.jar通过provided方式依赖god包。
god包中的SermantClassLoader负责加载core包中的类。
Sermant支持挂载多次,通过artifact区分,artifact默认为default。
通过顶层BootStrap类加载器来加载god包下的SermantManager管理n个artifact下的SermantClassLoader。
SermantClassLoader#loadClass:
SermantClassLoader继承URLClassLoader,支持从core包中加载类,父类加载器是AppClassLoader。
SermantClassLoader优先从core包中找类,然后才走双亲委派。
三、启动主流程
AgentCoreEntrance#install:启动主流程如下。
public static void install(String artifact, Map<String, Object> argsMap, Instrumentation instrumentation,
boolean isDynamic) throws Exception {
if (isDynamic) {
agentType = AgentType.AGENTMAIN.getValue();
}
artifactCache = artifact;
// 一个artifact对应一个AdviserInterface
adviserCache = new DefaultAdviser();
// 创建一个默认的logger(忽略)
LoggerFactory.initDefaultLogger(artifact);
// 类加载器管理 初始化
ClassLoaderManager.init(argsMap);
// 日志系统 初始化 ---> SermantBridgeHandler
LoggerFactory.init(artifact);
// Build the path index by startup configuration
BootArgsIndexer.build(argsMap);
// 加载config.properties
ConfigManager.initialize(argsMap);
// 加载BaseOperation类
OperationManager.initOperations();
// 加载BaseService类
ServiceManager.initServices();
// 事件系统 初始化
EventManager.init();
// 字节码增强 初始化
ByteEnhanceManager.init(instrumentation);
// 插件 初始化
PluginSystemEntrance.initialize(isDynamic);
// 注册本次增强的adviser
AdviserScheduler.registry(adviserCache);
// premain方式启动,使用插件执行增强
if (!isDynamic) {
ByteEnhanceManager.enhance();
}
// 发布Sermant启动事件
FrameworkEventCollector.getInstance().collectAgentStartEvent();
// 发布LOAD_COMPLETE通知
if (NotificationManager.isEnable()) {
NotificationManager.doNotify(new NotificationInfo(SermantNotificationType.LOAD_COMPLETE, null));
}
}
按照上面的主流程可以划分为四部分:
- 类加载器初始化,ClassLoaderManager.init;
- 业务服务初始化,LoggerFactory-日志,ConfigManager-配置,OperationManager-BaseOperation类,ServiceManager-BaseService类,EventManager-事件;
- 字节码增强安装,PluginSystemEntrance.initialize-插件初始化,AdviserScheduler-注册本次增强adviser,ByteEnhanceManager.enhance-安装增强;
- 后置处理,FrameworkEventCollector发送启动事件给backend,NotificationManager发布LOAD_COMPLETE给框架内部;(这部分忽略)
四、类加载器管理初始化
下图是Sermant的整体类加载器结构。
对应产物结构:
ClassLoaderManager#init:
- SermantClassLoader在AgentLauncher中构造,只能加载core.jar,这里增加了common.jar;
- FrameworkClassLoader用于加载implement.jar,Sermant框架对于core包中的接口实现,可以引入三方依赖,如zk、nacos,但是不会与用户的类冲突。父类加载器是SermantClassLoader;
- 创建PluginClassFinder,管理插件和插件类加载器PluginClassLoader,插件可能还有service模块,涉及ServiceClassLoader,这个在插件初始化中细看;
五、日志初始化
LoggerFactory#init:创建Logger实例。
通过FrameworkClassLoader加载框架implement.jar中的LoggerFactoryImpl,并执行init方法。
LoggerFactoryImpl#getLogger:核心点在于Logger实例加入了SermantBridgeHandler。
SermantBridgeHandler可以将WARN和ERROR级别日志,通过事件系统发送到backend。
在backend可以看到日志类型事件。
六、配置初始化
ConfigManager#initialize:JDK-SPI使用FrameworkClassLoader加载implement包下的LoadConfigStrategy实现,目前官方支持properties和yaml格式配置文件。
默认配置文件是agent/config.properties。
ConfigManager#doLoadConfig:JDK-SPI用SermantClassLoader加载core包中的BaseConfig实现,将配置注入BaseConfig。
BaseConfig是一个标记接口。
通过ConfigTypeKey注解修饰,配合配置解析,将配置项注入BaseConfig实现类。
运行阶段通过ConfigManager.getConfig(配置类)可以拿到配置。
七、BaseOperation初始化
BaseOperation标记接口。
OperationManager#initOperations:JDK-SPI通过FrameworkClassLoader加载BaseOperation实现,这表示BaseOperation的实现只能在框架内部的implement.jar中。
BaseOperation的实现不多,也不太重要,忽略。
八、BaseService初始化
BaseService接口包含启动和停止方法。
ServiceManager#initServices:
JDK-SPI使用FrameworkClassLoader加载implement.jar中的BaseService实现类。
如果agent.service.xxx.enabled开启该服务,则放入内存map管理,并调用BaseService#start。
这些BaseService接口定义在core中,实现在implement中。
如core定义GatewayClient与backend通讯客户端。
implement引入netty依赖,实现NettyGatewayClient。
运行期间可通过ServiceManager#getService获取服务。
如:
九、事件系统初始化
EventManager#init:
- 注册FrameworkEventCollector(框架核心事件采集)和LogEventCollector(日志事件采集);
- 开启定时任务,每隔30s采集所有EventCollector的事件,发送到backend;
框架中有N个EventCollector,负责不同的业务。
每个EventCollector有100容量的阻塞队列。
发送数据到EventCollector:如果队列满了,将直接发送至backend;如果未满,则由EventManager定时批量消费队列,发送到backend。
十、字节码增强安装
1、插件的结构
一般插件分为3个部分:
配置文件
配置文件非必须,会被解析到plugin的PluginConfig类中;
PluginConfig由JDK-SPI加载。
运行时可以通过PluginConfigManager获取。
插件主模块
plugin,插件主模块,用于定义切点和拦截逻辑,通过provided方式引入core包和宿主依赖。(可以compile引入三方依赖但是不建议)
插件定义
PluginDeclarer插件定义,ClassMatcher定义拦截类,InterceptorDeclarer定义拦截方法和拦截Interceptor。
插件定义会通过JDK-SPI加载。
比如SpringCloudMappingRegistryDeclarer拦截springmvc的RequestMapping注册,交给SpringCloudMappingRegistryInterceptor处理。
Interceptor拦截器操作ExecuteContext,可以实现多种逻辑:替换入参、跳过实际方法执行、替换出参等等。
插件服务接口定义
比如服务可见性插件,plugin声明CollectorService接口,继承PluginService,用于发送数据到backend。
运行时plugin通过PluginServiceManager获取CollectorService实现,实现会由service模块提供。
插件服务模块
service为plugin服务,非必须,通过provided方式引入core包和plugin包,可以compile引入三方依赖;
service实现CollectorService接口,引入fastjson三方依赖对数据进行序列化。
插件服务实现同样使用JDK-SPI加载。
2、ByteEnhanceManager初始化
ByteEnhanceManager#init:创建BufferedAgentBuilder。
BufferedAgentBuilder#actions缓存对bytebuddy的AgentBuilder的构造,用于后续回调。
BufferedAgentBuilder#build:除插件以外的AgentBuilder构造。
比如isOutputEnhancedClasses=true,可以输出增强class文件到enhancedClasses目录,用于排查问题。
3、插件初始化
3-1、主流程
插件通过config/plugins.yaml配置:
- plugins:静态插件,通过premain方式挂载时安装增强;
- dynamicPlugins:动态插件支持动态安装和卸载,通过agentmain方式挂载,其中active插件在挂载时直接安装,passive插件需要通过命令安装;
PluginSystemEntrance#initialize:
- 读取plugins.yaml配置文件;
- PluginManager根据plugin名称,初始化plugin;
PluginManager#initPlugins:循环每个插件名安装增强,不会重复安装。
PluginManager#executeInit:
根据插件名定位插件目录,针对每个插件创建一个PluginClassLoader,将plugin名称和PluginClassLoader封装为一个Plugin。
PluginManager#doInitPlugin:插件初始化分为以下几步
- 将plugin目录下的jar放入PluginClassLoader;
- 如果存在service目录,创建ServiceClassLoader,其parent为PluginClassLoader,放入Plugin;
- 加载PluginConfig,读取config配置;
- 加载PluginService并start;
- 将Plugin放入ClassLoaderManager的PluginClassFinder维护;
- 如果premain挂载,将plugin定义缓存到BufferedAgentBuilder的actions中;如果agentmain挂载,直接触发bytebuddy的AgentBuilder所有构造,安装active插件增强;
3-2、创建PluginClassLoader
ClassLoaderManager#createPluginClassLoader:
PluginClassLoader是URLClassLoader,用于加载当前插件的plugin目录下的jar,父类加载器是SermantClassLoader。
PluginClassLoader#loadClass:PluginClassLoader破坏双亲委派
- 优先尝试自己findClass,这里能加载plugin目录下的class;
- 其次尝试双亲委派加载,从SermantClassLoader(core/common)->AppClassLoader->...;
- 最终会尝试使用localClassLoader加载;
这个localClassLoader是sermant到用户类加载器的桥梁,Interceptor的类加载器是PluginClassLoader,如果需要在Interceptor中加载用户的类,则需要用到localClassLoader。
对比SkyWalking处理方式不同。InterceptorInstanceLoader是agent的ClassLoader到应用ClassLoader的桥梁,Interceptor的类加载器虽然是agent的classLoader,但是agent的classLoader的parent是应用ClassLoader(如LaunchedURLClassLoader)。
PluginClassLoader#getClassFromLocalClassLoader:
- 用户可以通过setLocalLoader,放入指定ClassLoader,用于后续类加载;
- 如果用户未设置,默认agent.config.useContextLoader=true,可使用线程上下文类加载器加载;
- 否则抛出ClassNotFound;
所以,springboot应用在插件Interceptor中可以加载LaunchedURLClassLoader负责的类。
// 线程id -> localClassLoader
private final HashMap<Long, ClassLoader> localLoader = new HashMap<>();
private Class<?> getClassFromLocalClassLoader(String name) {
// 优先取当前线程,显示塞入的ClassLoader
ClassLoader loader = localLoader.get(Thread.currentThread().getId());
// useContextLoader默认true,也可以用线程上下文类加载器
if (loader == null && useContextLoader/*default true*/) {
loader = Thread.currentThread().getContextClassLoader();
}
Class<?> clazz = null;
// 排除自己和ServiceClassLoader的情况,防止StackOverFlow
if (loader != null && !this.equals(loader) && !(loader instanceof ServiceClassLoader)) {
try {
clazz = loader.loadClass(name);
} catch (ClassNotFoundException e) {
LOGGER.log(Level.FINE, "Load class failed, msg is {0}", e.getMessage());
}
}
return clazz;
}
public void setLocalLoader(ClassLoader loader) {
localLoader.put(Thread.currentThread().getId(), loader);
}
public void removeLocalLoader() {
localLoader.remove(Thread.currentThread().getId());
}
3-3、创建ServiceClassLoader
PluginManager#loadServiceLibs:
如果plugin包含service.jar,则创建一个ServiceClassLoader,其parent是当前Plugin的PluginClassLoader。
ServiceClassLoader#loadClass:
- 优先尝试从service目录下加载;
- 其次尝试双亲委派 PluginClassLoader->SermantClassLoader->AppClassLoader... ;
所以ServiceClassLoader处于整个类加载结构的最底层,可以加载到所有类。
3-4、加载PluginConfig
PluginConfigManager在core包中,管理所有插件配置。
PluginConfigManager#loadPluginConfigs:
PluginConfig由JDK-SPI+PluginClassLoader加载,所以需要声明在plugin模块中。
3-5、加载PluginService
core模块的BaseService实现在implement包中,由FrameworkClassLoader加载,由ServiceManager管理。
PluginService是一个特殊BaseService,
如果插件存在service则PluginService由ServiceClassLoader加载,否则由PluginClassLoader加载。
即PluginService可以实现在plugin模块中,也可以实现在service模块中,一个PluginService只能有一种实现(可以通过SpiWeight注解声明优先级,但是框架里还没地方用)。
PluginService由PluginServiceManager管理。
3-6、转换Plugin为PluginDescription缓存到ByteEnhanceManager
PluginManager#doInitPlugin:这里分析premain启动,即静态挂载。
ByteEnhanceManager#enhanceStaticPlugin:
调用PluginCollector将Plugin转换为PluginDescription缓存到builder。
PluginCollector#getDescriptions:可通过两种途径得到PluginDescription
- 用户实现PluginDeclarer,转换为PluginDescription,属于高级api;
- 用户直接实现PluginDescription,框架通过PluginClassLoader+SPI加载得到,属于低级api,一般不用;
PluginCollector#combinePluginDeclarers:
- 使用PluginClassLoader加载插件下的所有PluginDeclarer;
- 将PluginDeclarer按照匹配类型分组,nameCombineMap根据类名精确匹配,combinedList模糊匹配;
- 创建AbstractPluginDescription返回;
注意:PluginDeclarer在sermant启动最后阶段加载,可以通过ServiceManager、PluginServiceManager、ConfigManager都能拿到SPI实现类,虽然一般不会在PluginDeclarer中这么做。
PluginCollector#createPluginDescription:
一个Plugin对应一个AbstractPluginDescription,这里的方法将在类加载时被调用后面再看。
AbstractPluginDescription组合了bytebuddy的RawMatcher和Transformer,即一个插件对应一个匹配器和一个Transformer。
public abstract class AbstractPluginDescription implements ElementMatcher<TypeDescription>, PluginDescription {
@Override
public boolean matches(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module,
Class<?> classBeingRedefined, ProtectionDomain protectionDomain) {
return matches(typeDescription);
}
}
public interface PluginDescription extends AgentBuilder.RawMatcher, AgentBuilder.Transformer {
}
4、注册AdviserInterface
AgentCoreEntrance#install:
对于每个artifact都会创建一个AdviserInterface(god)实现,即DefaultAdviser(core),这一步将当前artifact的Adviser加入AdviserScheduler。
注:当前Adviser里还是空,没加入任何Interceptor,需要再ByteEnhanceManager#enhance阶段加入。
public static void install(String artifact, Map<String, Object> argsMap, Instrumentation instrumentation,
boolean isDynamic) throws Exception {
// 一个artifact对应一个AdviserInterface
adviserCache = new DefaultAdviser();
// 类加载器管理 初始化
ClassLoaderManager.init(argsMap);
// .. 业务初始化
// 字节码增强 初始化
ByteEnhanceManager.init(instrumentation);
// 插件 初始化
PluginSystemEntrance.initialize(isDynamic);
// 注册本次增强的adviser
AdviserScheduler.registry(adviserCache);
// premain方式启动,插件增强定义
if (!isDynamic) {
ByteEnhanceManager.enhance();
}
// ...
}
AdviserScheduler(god)管理了n个artifact的AdviserInterface。
public class AdviserScheduler {
private static final ArrayList<AdviserInterface> ADVISERS = new ArrayList<>();
}
AdviserScheduler#onMethodEnter:运行时用户代码就能通过进入n个artifact的AdviserInterface。
public static ExecuteContext onMethodEnter(Object context, String adviceKey) throws Throwable {
ExecuteContext executeContext = (ExecuteContext) context;
// In multi-sermant scenario, method enter is executed in sequence
for (AdviserInterface currentAdviser : ADVISERS) {
if (currentAdviser != null) {
executeContext = currentAdviser.onMethodEnter(executeContext, adviceKey);
}
}
return executeContext;
}
后面会看到,AdviserScheduler在运行时就是用户ClassLoader到agent的桥梁。
5、使用bytebuddy api安装增强
ByteEnhanceManager#enhance:
在2.0版本sermant对启动速度进行优化,通过agent.config.preFilter.enable=true开启。
public static void enhance() {
// 加载unmatched_class_name.txt到内存,用于matcher加速匹配
cacheUnmatchedClass();
// 安装增强
builder.install(instrumentationCache);
// 注册ShutdownHook,将未匹配class,写入unmatched_class_name.txt
saveUnMatchedClass();
}
优化方式是,将类加载阶段未匹配增强的class名记录到unmatched_class_name.txt本地文件中,下次启动可以直接跳过这些类匹配。
org.yaml.snakeyaml.events.SequenceEndEvent
org.apache.commons.lang3.builder.HashCodeBuilder
javax.servlet.Filter
...
BufferedAgentBuilder#install:
将前面缓存的BuilderAction最终执行,使用butebuddy的AgentBuilder安装到Intrumentation上。
public ResettableClassFileTransformer install(Instrumentation instrumentation) {
AgentBuilder builder = new Default().disableClassFormatChanges();
for (BuilderAction action : actions) {
builder = action.process(builder);
}
return builder.installOn(instrumentation);
}
BufferedAgentBuilder#addPlugins:对于插件,将第三步的PluginDescription安装到AgentBuilder中,等类加载触发。
public BufferedAgentBuilder addPlugins(Iterable<PluginDescription> plugins) {
return addAction(new BuilderAction() {
@Override
public AgentBuilder process(AgentBuilder builder) {
AgentBuilder newBuilder = builder;
for (PluginDescription plugin : plugins) {
newBuilder = newBuilder.type(plugin/*RawMatcher*/)
.transform(plugin/*Transformer*/);
}
return newBuilder;
}
});
}
十一、类加载
agent启动阶段利用butebuddy将Transformer安装到Instrumentation,类加载阶段回调AbstractPluginDescription。
1、Class匹配
BufferedAgentBuilder#setIgnoredRule:IgnoredMatcher用于忽略class。
private BufferedAgentBuilder setIgnoredRule() {
return addAction(builder -> builder.ignore(new IgnoredMatcher(config)));
}
BufferedAgentBuilder.IgnoredMatcher#matches:以下class被忽略增强
- 2.0启动速度优化,unmatched_class_name.txt记录上次启动未匹配class;
- 忽略ServiceClassLoader加载且在agent.config.serviceInjectList配置中的class;
- 忽略array和原始类型;
- 忽略sermant其他classloader加载的类;
- 根据agent.config.ignoredPrefixes忽略类名前缀;
- 根据agent.config.ignoredInterfaces忽略接口实现类;
@Override
public boolean matches(TypeDescription typeDesc, ClassLoader classLoader, JavaModule javaModule,
Class<?> classBeingRedefined, ProtectionDomain protectionDomain) {
// 2.0优化,unmatched_class_name.txt上次未匹配class,直接跳过
if (unMatchedClassCache.containsKey(typeDesc.getActualName())) {
return true;
}
// ServiceClassLoader加载的class,在agent.config.serviceInjectList中,跳过
if (!checkInjectList(typeDesc, classLoader)) {
return false;
}
// array 原始类型 sermant的其他classloader加载的类
// ignoredPrefixes忽略配置前缀名称类
// ignoredInterfaces忽略接口实现类
return isArrayOrPrimitive(typeDesc) || checkClassLoader(typeDesc, classLoader)
|| isIgnoredPrefixes(typeDesc) || isIgnoredInterfaces(typeDesc);
}
默认情况下ignoredInterfaces配置了spring的org.springframework.cglib.proxy.Factory,避免了spring创建的动态代理对象被二次增强。
agent.config.ignoredInterfaces=org.springframework.cglib.proxy.Factory
PluginCollector#doMatch:接下来正向匹配哪些class要被增强
- 如果PluginDeclarer是类名精确匹配,直接通过nameCombinedMap执行O(1)复杂度匹配;
- 反之,回调PluginDeclarer的ClassMatcher对TypeDescription进行匹配;
private static AbstractPluginDescription createPluginDescription(Plugin plugin,
Map<String, List<PluginDeclarer>> nameCombinedMap, List<PluginDeclarer> combinedList) {
return new AbstractPluginDescription() {
// ...
@Override
public boolean matches(TypeDescription target) {
final String typeName = target.getActualName();
doMatch(target, typeName, combinedList, nameCombinedMap);
return nameCombinedMap.containsKey(typeName);
}
};
}
private static void doMatch(TypeDescription target, String typeName, List<PluginDeclarer> combinedList,
Map<String, List<PluginDeclarer>> nameCombinedMap) {
for (PluginDeclarer declarer : combinedList) {
if (matchTarget(declarer.getClassMatcher(), target)) { // 执行class匹配
List<PluginDeclarer> declarers = nameCombinedMap.computeIfAbsent(typeName,
key -> new ArrayList<>());
if (!declarers.contains(declarer)) {
declarers.add(declarer);
}
}
}
}
PluginCollector#matchTarget:记录了未匹配class到内存,待ShutdownHook阶段刷写到unmatched_class_name.txt。
private static boolean matchTarget(ElementMatcher<TypeDescription> matcher, TypeDescription target) {
boolean result = matcher.matches(target);
// agent.config.preFilter.enable=true 记录未匹配class
if (IS_PRE_FILTER_ENABLE && !result) {
FileUtils.addToUnmatchedClassCache(target.getActualName());
}
return result;
}
2、transform修改字节码
PluginCollector#doTransform:
- 循环class匹配的PluginDeclarer,获取class的方法增强声明InterceptDeclarer集合;
- 获取InterceptDeclarer会将用户classloader,比如springboot的LaunchedURLClassLoader放入PluginClassLoader的local临时ClassLoader,所以getInterceptDeclarers中能加载到用户class;
- 调用ReentrantTransformer#transform;
private static AbstractPluginDescription createPluginDescription(Plugin plugin,
Map<String, List<PluginDeclarer>> nameCombinedMap, List<PluginDeclarer> combinedList) {
return new AbstractPluginDescription() {
@Override
public Builder<?> transform(Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader,
JavaModule javaModule, ProtectionDomain protectionDomain) { // 类加载阶段,构建Transform
return doTransform(builder, typeDescription, classLoader, javaModule, protectionDomain, nameCombinedMap,
plugin);
}
};
}
private static Builder<?> doTransform(Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader,
JavaModule javaModule, ProtectionDomain protectionDomain, Map<String, List<PluginDeclarer>> nameCombinedMap,
Plugin plugin) {
final List<PluginDeclarer> pluginDeclarers = nameCombinedMap.get(typeDescription.getActualName());
final List<InterceptDeclarer> interceptDeclarers = new ArrayList<>();
for (PluginDeclarer pluginDeclarer : pluginDeclarers) {
ClassLoader loader = pluginDeclarer.getClass().getClassLoader();
if (loader instanceof PluginClassLoader) {
PluginClassLoader pluginClassLoader = (PluginClassLoader) loader;
pluginClassLoader.setLocalLoader(classLoader); // 将原始的class的类加载器,放入local
interceptDeclarers.addAll(Arrays.asList(
pluginDeclarer.getInterceptDeclarers(ClassLoader.getSystemClassLoader())));
pluginClassLoader.removeLocalLoader();
}
// ...
}
return new ReentrantTransformer(interceptDeclarers.toArray(new InterceptDeclarer[0]), plugin)
.transform(builder, typeDescription, classLoader, javaModule, protectionDomain);
}
AbstractTransformer#transform:允许在匹配class成功的情况下,插件Declarer不返回InterceptDeclarer,则不进行任何方法增强。
public Builder<?> transform(Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader,
JavaModule javaModule, ProtectionDomain protectionDomain) {
if (interceptDeclarers == null || interceptDeclarers.length == 0) {
return builder;
}
return enhanceMethods(builder, typeDescription, classLoader);
}
AbstractTransformer#enhanceMethods:循环每个非抽象native方法执行增强。
private DynamicType.Builder<?> enhanceMethods(DynamicType.Builder<?> builder, TypeDescription typeDesc,
ClassLoader classLoader) {
final MethodList<InDefinedShape> declaredMethods = typeDesc.getDeclaredMethods();
DynamicType.Builder<?> newBuilder = builder;
for (MethodDescription.InDefinedShape methodDesc : declaredMethods) {
if (methodDesc.isNative() || methodDesc.isAbstract()) {
continue;
}
newBuilder = enhanceMethod(newBuilder, methodDesc, classLoader);
}
return newBuilder;
}
AbstractTransformer#enhanceMethod:
- 对于每个method匹配InterceptDeclarer,获取Interceptor,注意这里传入的classloader是被增强类的classloader,在InterceptDeclarer获取Interceptor时可以使用;
- 执行resolve对方法增强,对于不同类型方法走不同模板class,如实例方法走TemplateForMember;
private DynamicType.Builder<?> enhanceMethod(DynamicType.Builder<?> builder,
MethodDescription.InDefinedShape methodDesc, ClassLoader classLoader) {
// 1. 获取interceptor
final List<Interceptor> interceptors = getInterceptors(methodDesc, classLoader);
if (interceptors.isEmpty()) {
return builder;
}
// 2. 方法增强
try {
if (methodDesc.isStatic()) {
return resolve(builder, methodDesc, interceptors, TemplateForStatic.class, classLoader);
} else if (methodDesc.isConstructor()) {
return resolve(builder, methodDesc, interceptors, TemplateForCtor.class, classLoader);
} else {
return resolve(builder, methodDesc, interceptors, TemplateForMember.class, classLoader);
}
} catch (NoSuchFieldException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
LOGGER.warning(String.format(Locale.ROOT, "Enhance [%s] failed for [%s], caused by [%s]. ",
MethodKeyCreator.getMethodDescKey(methodDesc), e.getClass().getName(), e.getMessage()));
}
return builder;
}
private List<Interceptor> getInterceptors(MethodDescription.InDefinedShape methodDesc, ClassLoader classLoader) {
final List<Interceptor> interceptors = new ArrayList<>();
for (InterceptDeclarer declarer : interceptDeclarers) {
// 1. 匹配方法
if (!declarer.getMethodMatcher().matches(methodDesc)) {
continue;
}
// 2. 匹配成功,获取Interceptor
interceptors.addAll(Arrays.asList(declarer.getInterceptors(classLoader)));
}
return interceptors;
}
ReentrantTransformer#resolve:
- 拼接adviceKey=template类名+用户类+增强方法签名+用户类的classloader;
- BaseAdviseHandler(core) 存储当前artifact下每个方法对应的Interceptor集合,这将在运行时用到;
- 最后将被增强方法和模板class注入bytebuddy的Builder;
@Override
protected Builder<?> resolve(Builder<?> builder, InDefinedShape methodDesc, List<Interceptor> interceptors,
Class<?> templateCls, ClassLoader classLoader)
throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException {
// 增强key=template类名+用户类+增强方法签名+用户类的classloader
final String adviceKey = getAdviceKey(templateCls, classLoader, methodDesc);
// BaseAdviseHandler,artifact级别,增强key下的Interceptor
List<Interceptor> interceptorsForAdviceKey = BaseAdviseHandler.getInterceptorListMap()
.computeIfAbsent(adviceKey, key -> new ArrayList<>());
// plugin级别,增强key下的interceptor的className
Set<String> createdInterceptorForAdviceKey = plugin.getInterceptors()
.computeIfAbsent(adviceKey, key -> new HashSet<>());
for (Interceptor interceptor : interceptors) {
if (checkInterceptor(adviceKey, interceptor.getClass().getCanonicalName())) {
interceptorsForAdviceKey.add(interceptor); // 【interceptor加入BaseAdviseHandler】
createdInterceptorForAdviceKey.add(interceptor.getClass().getCanonicalName());
}
}
if (checkAdviceLock(adviceKey)) {
// 安装增强
return builder.visit(Advice.to(templateCls).on(ElementMatchers.is(methodDesc)));
}
return builder;
}
public class BaseAdviseHandler {
private static final Map<String, List<Interceptor>> INTERCEPTOR_LIST_MAP = new ConcurrentHashMap<>();
}
十二、增强方法执行
以实例方法为例,TemplateForMember模板方法如下。
public class TemplateForMember {
/**
* The preceding trigger point of method
*
* @param cls enhanced class
* @param obj the object being enhanced
* @param method the method being enhanced
* @param methodKey method key, which is used to find template class
* @param arguments arguments of method
* @param adviceKey advice class name
* @param context execute context
* @param isSkip Whether to skip the main execution of method
* @return Skip result
* @throws Throwable execute exception
*
*/
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
public static boolean onMethodEnter(@Advice.Origin Class<?> cls,
@Advice.This(typing = Assigner.Typing.DYNAMIC) Object obj,
@Advice.Origin Method method, @Advice.Origin("#t\##m#s") String methodKey,
@Advice.AllArguments(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object[] arguments,
@Advice.Local(value = "_ADVICE_KEY_$SERMANT_LOCAL") String adviceKey,
@Advice.Local(value = "_EXECUTE_CONTEXT_$SERMANT_LOCAL") Object context,
@Advice.Local(value = "_IS_SKIP_$SERMANT_LOCAL") Boolean isSkip
) throws Throwable {
adviceKey = "TemplateForMember_" + Integer.toHexString(methodKey.hashCode()) + "_" + cls.getClassLoader();
context = ExecuteContext.forMemberMethod(obj, method, arguments, null, null);
context = AdviserScheduler.onMethodEnter(context, adviceKey);
arguments = ((ExecuteContext) context).getArguments();
isSkip = ((ExecuteContext) context).isSkip();
return isSkip;
}
/**
* The post trigger point of method
*
* @param result Method execution result
* @param throwable Method execution exception
* @param adviceKey advice class name
* @param context execute context
* @param isSkip Whether to skip the main execution of method
* @throws Throwable execute exception
*/
@Advice.OnMethodExit(onThrowable = Throwable.class)
public static void onMethodExit(@Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object result,
@Advice.Thrown(readOnly = false) Throwable throwable,
@Advice.Local(value = "_ADVICE_KEY_$SERMANT_LOCAL") String adviceKey,
@Advice.Local(value = "_EXECUTE_CONTEXT_$SERMANT_LOCAL") Object context,
@Advice.Local(value = "_IS_SKIP_$SERMANT_LOCAL") Boolean isSkip) throws Throwable {
context = isSkip ? context : ((ExecuteContext) context).afterMethod(result, throwable);
context = AdviserScheduler.onMethodExit(context, adviceKey);
result = ((ExecuteContext) context).getResult();
if (((ExecuteContext) context).isChangeThrowable()) {
throwable = ((ExecuteContext) context).getThrowable();
}
}
}
如果打开debug,输出被增强class如下:
public class UserService {
public UserService() {
}
public UserModel getUser(String var1) {
String var2;
ExecuteContext var3;
Boolean var4;
UserModel var6;
Throwable var12;
label32: {
var2 = null;
var3 = null;
var4 = null;
var2 = "TemplateForMember_" + Integer.toHexString("com.xxx.common.service.UserService#getUser(java.lang.String)".hashCode()) + "_" + UserService.class.getClassLoader();
var3 = ExecuteContext.forMemberMethod(this, UserService.class.getMethod("getUser", String.class), new Object[]{var1}, (Map)null, (Map)null);
var3 = AdviserScheduler.onMethodEnter(var3, var2);
var1 = (String)((ExecuteContext)var3).getArguments()[0];
var4 = ((ExecuteContext)var3).isSkip();
boolean var5 = var4;
UserModel var10000;
if (var5) {
var10000 = null;
} else {
String id = var1;
try {
UserModel userModel = new UserModel();
userModel.setId(id);
var10000 = userModel;
} catch (Throwable var11) {
var12 = var11;
var6 = null;
break label32;
}
}
var6 = var10000;
var12 = null;
}
var3 = var4 ? var3 : ((ExecuteContext)var3).afterMethod(var6, var12);
var3 = AdviserScheduler.onMethodExit(var3, var2);
var6 = (UserModel)((ExecuteContext)var3).getResult();
if (((ExecuteContext)var3).isChangeThrowable()) {
var12 = ((ExecuteContext)var3).getThrowable();
}
if (var12 != null) {
throw var12;
} else {
return var6;
}
}
}
- var2=拼接adviceKey,var3=创建ExecuteContext上下文对象;
- AdviserScheduler#onMethodEnter,执行方法前置拦截,出参ExecuteContext可以设置skip,跳过目标方法执行;
- 执行目标方法,记录方法执行异常,var12;
- AdviserScheduler#onMethodExit,执行方法后置拦截,出参ExecuteContext可以设置新出参getResult,也可以设置新异常getThrowable;
这里最重要的是AdviserScheduler如何调度到m个artifact下的n个Interceptor。
AdviserScheduler在god包中,被BootStrapClassLoader加载,是真正的全局单例class。AdviserScheduler即能被agent加载到,用于注册Adviser;也能被用户加载到,用于执行Adviser。
AdviserInterface在god包中,但AdviserInterface的实现DefaultAdviser在core包中,被FrameworkClassLoader加载,是artifact级别单例class。
public class AdviserScheduler {
private static final ArrayList<AdviserInterface> ADVISERS = new ArrayList<>();
public static ExecuteContext onMethodEnter(Object context, String adviceKey) throws Throwable {
ExecuteContext executeContext = (ExecuteContext) context;
// In multi-sermant scenario, method enter is executed in sequence
for (AdviserInterface currentAdviser : ADVISERS) {
if (currentAdviser != null) {
executeContext = currentAdviser.onMethodEnter(executeContext, adviceKey);
}
}
return executeContext;
}
}
DefaultAdviser调用BaseAdviseHandler#handleMethodEnter静态方法执行方法前置拦截。
注意这里传入了异常处理回调方法,记录异常,这保证了agent逻辑一般不会影响业务。
public class DefaultAdviser implements AdviserInterface {
private void logError(String scene, ExecuteContext context, Interceptor interceptor, Throwable throwable) {
LOGGER.log(Level.SEVERE, String.format(Locale.ROOT, "An error occurred %s [%s] in interceptor [%s]: ", scene,
MethodKeyCreator.getMethodKey(context.getMethod()), interceptor.getClass().getName()), throwable);
}
@Override
public ExecuteContext onMethodEnter(ExecuteContext context, String adviceKey) throws Throwable {
return BaseAdviseHandler.handleMethodEnter(context, adviceKey, new BaseAdviseHandler.ExceptionHandler() {
@Override
public void handle(ExecuteContext context, Interceptor interceptor, Throwable throwable) {
logError("before executing", context, interceptor, throwable);
}
});
}
}
BaseAdviseHandler#handleMethodEnter:
BaseAdviseHandler在类加载transform阶段,保存了adviceKey对应Interceptor关系,自然可以执行Interceptor逻辑。
public class BaseAdviseHandler {
private static final Map<String, List<Interceptor>> INTERCEPTOR_LIST_MAP = new ConcurrentHashMap<>();
public static ExecuteContext handleMethodEnter(ExecuteContext context, String adviceKey,
ExceptionHandler enterHandler) throws Throwable {
List<Interceptor> interceptorList = INTERCEPTOR_LIST_MAP.get(adviceKey);
if (interceptorList == null) {
return context;
}
context.setInterceptorIterator(interceptorList.listIterator());
return handleMethodEnter(context, context.getInterceptorIterator(), enterHandler);
}
}
BaseAdviseHandler#handleMethodEnter:循环执行每个Interceptor。
每个Interceptor返回的ExecuteContext会作为下一个Interceptor的入参;
如果ExecuteContext返回skip,直接跳过剩余Interceptor;
如果Interceptor发生异常,被catch并打印日志;
可以设置ExecuteContext#getThrowableOut,在所有Interceptor执行完毕后抛出异常;
public static ExecuteContext handleMethodEnter(ExecuteContext context, ListIterator<Interceptor> interceptorItr,
ExceptionHandler enterHandler) throws Throwable {
ExecuteContext newContext = context;
while (interceptorItr.hasNext()) {
try {
final Interceptor interceptor = interceptorItr.next();
try {
final ExecuteContext tempContext = interceptor.before(newContext);
if (tempContext != null) {
newContext = tempContext;
}
if (newContext.isSkip()) {
return newContext;
}
} catch (Throwable t) {
enterHandler.handle(context, interceptor, t);
}
} catch (Exception exception) {
LOGGER.log(Level.SEVERE, "Exception occurs when method enter.", exception);
return newContext;
}
if (newContext.getThrowableOut() != null) {
throw newContext.getThrowableOut();
}
}
return newContext;
}
总结
1、类加载器
Sermant类加载结构如下:
- AppClassLoader:agent的类加载器,启动后将god.jar安装到BootStrap顶层类加载器,创建SermantClassLoader反射加载core.jar中的真实启动类AgentCoreEntrance;
- BootStrapClassLoader:顶层类加载器,加入god包后实现了n个artifact的sermant实例挂载(SermantManager),同时god包也是用户类到agent类的桥梁(AdviserScheduler);
- SermantClassLoader:每个artifact对应一个SermantClassLoader,负责加载common和core包,core只依赖god和bytebuddy。如果依赖三方依赖实现能力,需要声明接口,都通过JDK-SPI获取implement实现,如BaseService。SermantClassLoader是sermant的顶层类加载器,与用户classloader(如springboot)隔离,双方加载自己的class不会互相干扰,不会发生类冲突;
- FrameworkClassLoader:加载implement包,implement包是sermant框架对于core包中核心服务实现,引入了三方依赖(如netty),与插件类加载器隔离,不会发生类冲突;
- PluginClassLoader:每个插件对应一个PluginClassLoader,负责加载插件中的类,通过临时设置的localClassLoader或线程上下文classloader可以加载用户class(这是agent到用户class的桥梁),插件之间相互隔离。provided引入core和宿主依赖,定义拦截类、拦截方法、拦截器。需要三方依赖的情况声明PluginService(虽然1.4之后plugin不由appclassloader加载,允许compile依赖三方),通过SPI加载service模块中的实现;
- ServiceClassLoader:如果插件需要引入三方依赖实现功能,可引入service模块实现plugin模块声明的PluginService接口,和FrameworkClassLoader职责类似;
2、premain主流程
premain的启动流程有严格顺序。
- premain方法
将god包安装到BootStrap类加载器;
创建artifact对应SermantClassLoader;
反射执行AgentCoreEntrance;
- 类加载器初始化
ClassLoaderManager.init,将common加入SermantClassLoader,创建FrameworkClassLoader;
- 业务服务初始化
LoggerFactory-日志:SermantBridgeHandler可以将WARN和ERROR级别日志,通过事件系统发送到backend;
ConfigManager-配置:JDK-SPI用SermantClassLoader加载core包中的BaseConfig实现,将配置注入BaseConfig;
OperationManager-BaseOperation类:JDK-SPI用FrameworkClassLoader加载BaseOperation实现,BaseOperation实现不多可以忽略;
ServiceManager-BaseService类:JDK-SPI用FrameworkClassLoader加载implement.jar中的BaseService实现,需要通过agent.service.[模块名].enabled配置开启,这里会调用BaseService#start,BaseService比较重要的实现如GatewayClient,实现与backend通讯;
EventManager-事件:EventManager管理N个Collector,每隔30s采集所有Collector缓存队列(100)中的事件,发送给backend;
- 字节码增强安装
PluginSystemEntrance.initialize-插件初始化:
1)根据插件配置文件plugin.yaml,创建N个Plugin,每个Plugin对应一个plugin的产物目录;
2)每个Plugin有一个PluginClassLoader加载plugin.jar,如果存在service目录,创建ServiceClassLoader,加载service.jar;
3)JDK-SPI用PluginClassLoader加载PluginConfig实现,注入插件配置;
4)JDK-SPI加载PluginService并start,如果插件存在service则由ServiceClassLoader加载,否则由PluginClassLoader加载;
5)使用PluginClassLoader加载插件下的所有PluginDeclarer,封装为AbstractPluginDescription,缓存到BufferedAgentBuilder;
AdviserScheduler-注册本次增强AdviserInterface:
1)AdviserScheduler由BootStrapClassLoader加载,静态管理n个artifact下的AdviserInterface;
2)AdviserInterface的实现是DefaultAdviser,DefaultAdviser通过BaseAdviseHandler管理n个方法下的m个Interceptor;
ByteEnhanceManager.enhance-安装增强:将前面缓存的BuilderAction最终执行(如PluginDefinition),使用butebuddy的AgentBuilder安装到Intrumentation上。
3、类加载
1)对被加载类执行class正逆向匹配,得到类对应的PluginDeclarer集合;
2)调用PluginDeclarer的getInterceptDeclarers方法,得到InterceptDeclarer集合;
3)循环类的每个方法,调用InterceptDeclarerInterceptDeclarer#getMethodMatcher匹配方法;
4)方法匹配成功,调用InterceptDeclarer#getInterceptors,获取interceptor,拼接adviceKey注册到BaseAdviseHandler中,供运行时调用;
5)将被增强方法和模板class注入bytebuddy的Builder,执行增强;
4、增强方法执行
由于ExecuteContext、AdviserScheduler都能被BootStrapClassLoader加载,所以用户代码能进入agent的Interceptor逻辑。
被增强的用户方法,会被AdviserScheduler中的Adviser拦截,每个Adviser包含了一个artifact下的n个该adviceKey方法下的Interceptor。
var2 = "TemplateForMember_" + Integer.toHexString("com.xxx.common.service.UserService#getUser(java.lang.String)".hashCode()) + "_" + UserService.class.getClassLoader();
var3 = ExecuteContext.forMemberMethod(this, UserService.class.getMethod("getUser", String.class), new Object[]{var1}, (Map)null, (Map)null);
var3 = AdviserScheduler.onMethodEnter(var3, var2);
var4 = ((ExecuteContext)var3).isSkip();
var1 = (String)((ExecuteContext)var3).getArguments()[0];
if (var4) {
var6 = null;
} else {
try {
var6 = 用户代码逻辑;
} catch (Throwable e) {
var12 = e;
var6 = null;
}
}
var3 = var4 ? var3 : ((ExecuteContext)var3).afterMethod(var6, var12);
var3 = AdviserScheduler.onMethodExit(var3, var2);
var6 = (UserModel)((ExecuteContext)var3).getResult();
if (((ExecuteContext)var3).isChangeThrowable()) {
var12 = ((ExecuteContext)var3).getThrowable();
}
if (var12 != null) {
throw var12;
} else {
return var6;
}
方法增强逻辑包含:
- enter阶段,ExecuteContext#isSkip,跳过用户代码;
- enter阶段,ExecuteContext#getArguments,替换方法入参;
- exit阶段,ExecuteContext#getResult,替换方法出参;
- exit阶段,ExecuteContext#getThrowable,替换方法异常;
此外,如果Interceptor发生异常,DefaultAdviser会记录异常日志,不会阻断流程。
欢迎大家评论或私信讨论问题。
本文原创,未经许可不得转载。
欢迎关注公众号【程序猿阿越】。
转载自:https://juejin.cn/post/7398789000222605351