SkyWalking 8原始碼(一):Agent位元組碼增強的實現
技術標籤:java
準備
1、下載skywalking的開原始碼到本地,用idea開啟,將他的資料夾改成source檔案,否則無法除錯,會變成藍色:
maven自動下載包和外掛。
2、下載外掛translation可以翻譯英文註釋。
3、找到啟動檔案,ApplicationStartUp,在apm-webapp檔案下,是此專案的啟動檔案。
4、入口類 org.skywalking.apm.agent.SkyWalkingAgent
1、apm-sniffer包下的apm-agent包中的skywalkingagent類的premain方法為入口:
public static void premain(String agentArgs, Instrumentation instrumentation) throws PluginException {
......
}
(1)agentArgs 是 premain 函式得到的程式引數,隨同 “– javaagent”一起傳入。
(2)instrumentation 是一個 java.lang.instrument.Instrumentation 的例項,由 JVM 自動傳入。
java.lang.instrument.Instrumentation 是 instrument 包中定義的一個介面,也是這個包的核心部分,
集中了其中幾乎所有的功能方法,例如類定義的轉換和操作等等。
skywalking利用了javaagent實現了虛擬機器級別的aop,利用位元組碼工具bytebuddy實現位元組碼增強。
2、初始化:
SnifferConfigInitializer.initializeCoreConfig(agentArgs); //讀取配置檔案資訊,環境變數資訊,vm option引數
3、載入外掛:
skywalking通過外掛載入機制,實現對不同中介軟體等的監控,即載入對應的外掛即可
pluginFinder = new PluginFinder(new PluginBootstrap().loadPlugins());
在PluginBootstrap類中的loadPlugins()中:
//先宣告一個資源載入器resolver,在resolver中的getResources()會查詢skywalking-plugin.def檔案中定義的類
PluginResourcesResolver resolver = new PluginResourcesResolver();
List<URL> resources = resolver.getResources();
//迴圈load skywalking-plugin.def ,並且解析該檔案
for (URL pluginUrl : resources) {
try {
PluginCfg.INSTANCE.load(pluginUrl.openStream());
} catch (Throwable t) {
logger.error(t, "plugin file [{}] init failure.", pluginUrl);
}
}
//載入skywalking-plugin.def的類,儲存到list中
List<PluginDefine> pluginClassList = PluginCfg.INSTANCE.getPluginClassList();
List<AbstractClassEnhancePluginDefine> plugins = new ArrayList<AbstractClassEnhancePluginDefine>();
//通過反射建立這些類的例項
for (PluginDefine pluginDefine : pluginClassList) {
try {
logger.debug("loading plugin class {}.", pluginDefine.getDefineClass());
AbstractClassEnhancePluginDefine plugin = (AbstractClassEnhancePluginDefine) Class.forName(pluginDefine.getDefineClass(), true, AgentClassLoader
.getDefault()).newInstance();
plugins.add(plugin);
} catch (Throwable t) {
logger.error(t, "load plugin [{}] failure.", pluginDefine.getDefineClass());
}
}
//最後返回plugins
return plugins;
將返回的plugins傳給pluginFinder類,作為構造引數,pluginFinder類中有方法buildMatch()來判斷需要修改哪些類的位元組碼(因為不是每一個類都需要增強):
//定義了集合
//pluginFinder將外掛分類儲存在兩個集合中,分別是:按名字分類和按其他輔助資訊分類
private final Map<String, LinkedList<AbstractClassEnhancePluginDefine>> nameMatchDefine = new HashMap<String, LinkedList<AbstractClassEnhancePluginDefine>>();
private final List<AbstractClassEnhancePluginDefine> signatureMatchDefine = new ArrayList<AbstractClassEnhancePluginDefine>();
private final List<AbstractClassEnhancePluginDefine> bootstrapClassMatchDefine = new ArrayList<AbstractClassEnhancePluginDefine>();
//構造方法
public PluginFinder(List<AbstractClassEnhancePluginDefine> plugins) {
for (AbstractClassEnhancePluginDefine plugin : plugins) {
//抽象方法enhanceClass方法定義在外掛的抽象基類AbstractClassEnhancePluginDefine中,每一個外掛必須去實現這個類中的方法
ClassMatch match = plugin.enhanceClass(); //故enhanceClass是每個外掛都會自己去實現的方法,指定需要增強的類
if (match == null) {
continue;
}
if (match instanceof NameMatch) {
NameMatch nameMatch = (NameMatch) match;
LinkedList<AbstractClassEnhancePluginDefine> pluginDefines = nameMatchDefine.get(nameMatch.getClassName());
if (pluginDefines == null) {
pluginDefines = new LinkedList<AbstractClassEnhancePluginDefine>();
nameMatchDefine.put(nameMatch.getClassName(), pluginDefines);
}
pluginDefines.add(plugin);
} else {
signatureMatchDefine.add(plugin);
}
if (plugin.isBootstrapInstrumentation()) {
bootstrapClassMatchDefine.add(plugin);
}
}
}
//typeDescription是bytebuddy的內建介面,是對類的完整描述,包含了類的全類名
//傳入typeDescription,返回可以運用於typeDescription的類的外掛
public List<AbstractClassEnhancePluginDefine> find(TypeDescription typeDescription) {
List<AbstractClassEnhancePluginDefine> matchedPlugins = new LinkedList<AbstractClassEnhancePluginDefine>();
String typeName = typeDescription.getTypeName();
//根據名字資訊匹配查詢
if (nameMatchDefine.containsKey(typeName)) {
matchedPlugins.addAll(nameMatchDefine.get(typeName));
}
//通過除了名字之外的輔助資訊,在signatureMatchDefine集合中查詢
for (AbstractClassEnhancePluginDefine pluginDefine : signatureMatchDefine) {
IndirectMatch match = (IndirectMatch) pluginDefine.enhanceClass();
if (match.isMatch(typeDescription)) {
matchedPlugins.add(pluginDefine);
}
}
return matchedPlugins;
}
public ElementMatcher<? super TypeDescription> buildMatch() {
//設定匹配的規則,名字是否相同,通過名字直接匹配
ElementMatcher.Junction judge = new AbstractJunction<NamedElement>() {
@Override
public boolean matches(NamedElement target) {
return nameMatchDefine.containsKey(target.getActualName());
}
};
judge = judge.and(not(isInterface())); //介面不增強,排除掉
//如果無法確定類的全限定名,則通過註解、回撥資訊等輔助方法間接匹配
for (AbstractClassEnhancePluginDefine define : signatureMatchDefine) {
ClassMatch match = define.enhanceClass();
if (match instanceof IndirectMatch) {
judge = judge.or(((IndirectMatch) match).buildJunction());
}
}
return new ProtectiveShieldMatcher(judge);
}
以dubbo外掛為例:
public class WrapperInstrumentation extends ClassStaticMethodsEnhancePluginDefine { //間接繼承了基類
@Override
public StaticMethodsInterceptPoint[] getStaticMethodsInterceptPoints() {
return new StaticMethodsInterceptPoint[] {
new StaticMethodsInterceptPoint() {
@Override
public ElementMatcher<MethodDescription> getMethodsMatcher() {
return named("makeWrapper");
}
@Override
public String getMethodsInterceptor() {
return "org.apache.skywalking.apm.plugin.dubbo.patch.MakeWrapperInterceptor";
}
@Override
public boolean isOverrideArgs() {
return false;
}
}
};
}
@Override
protected ClassMatch enhanceClass() { //重寫了基類的enhanceClass()方法,指定了需要增強的類
return byName("com.alibaba.dubbo.common.bytecode.Wrapper");
}
}
4、通過bytebuddy中的agentBuilder來生成一個agent,並執行相關邏輯:
利用bytebuddy的API生成一個代理,並執行transform方法和監聽器Listener(主要是日誌相關)。
在premain中,通過鏈式呼叫,被builderMatch()匹配到的類都會執行transform方法,transform定義了位元組碼增強的邏輯:
agentBuilder.type(pluginFinder.buildMatch())
.transform(new Transformer(pluginFinder))
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.with(new Listener())
.installOn(instrumentation);
private static class Transformer implements AgentBuilder.Transformer {
private PluginFinder pluginFinder;
Transformer(PluginFinder pluginFinder) {
this.pluginFinder = pluginFinder;
}
@Override
public DynamicType.Builder<?> transform(final DynamicType.Builder<?> builder,
final TypeDescription typeDescription,
final ClassLoader classLoader,
final JavaModule module) {
//typeDescription是bytebuddy的內建介面,是對類的完整描述,包含了類的全類名
//這裡返回的是所有可以運用於typeDescription的類的外掛【一個類可能有多個外掛,見def檔案】
List<AbstractClassEnhancePluginDefine> pluginDefines = pluginFinder.find(typeDescription);
if (pluginDefines.size() > 0) {
DynamicType.Builder<?> newBuilder = builder;
EnhanceContext context = new EnhanceContext();
for (AbstractClassEnhancePluginDefine define : pluginDefines) {
DynamicType.Builder<?> possibleNewBuilder = define.define( //最重要的define方法,在AbstractClassEnhancePluginDefine中定義
typeDescription, newBuilder, classLoader, context);
if (possibleNewBuilder != null) {
newBuilder = possibleNewBuilder;
}
}
if (context.isEnhanced()) {
LOGGER.debug("Finish the prepare stage for {}.", typeDescription.getName());
}
return newBuilder;
}
LOGGER.debug("Matched class {}, but ignore by finding mechanism.", typeDescription.getTypeName());
return builder; //返回位元組碼
}
}
需要補充的是:
AbstractClassEnhancePluginDefine , 做為所有的外掛都要繼承實現的基礎, 抽象類AbstractClassEnhancePluginDefine 具體定義了什麼行為, 這些行為又有哪些意義?什麼地方在呼叫這個方法?
AbstractClassEnhancePluginDefine 中有四個方法
- enhance 抽象方法, 抽象類中無實現, 子類去實現,具體的增強的邏輯
- enhanceClass 抽象方法, 抽象類中無實現,子類去實現,具體的要增強的類的 Match
- witnessClasses 也是可以被過載的一個方法,子類可過載
- define 例項方法,它主要做了下面的幾次事 ( 入口方法)
1 找到增強外掛類的所有顯式指定的 WitnessClasses - 使用者自己過載顯式指定
2 呼叫 enhance 方法, 執行真正的外掛的增強邏輯,返回新的 DynamicType.Builder
3 設定上下文, 標記初始化定義步驟已完成
4 最後再返回新的 第 2 中的增強後的 newClassBuilder
public abstract class AbstractClassEnhancePluginDefine {
private static final ILog LOGGER = LogManager.getLogger(AbstractClassEnhancePluginDefine.class);
public DynamicType.Builder<?> define(TypeDescription typeDescription, DynamicType.Builder<?> builder,
ClassLoader classLoader, EnhanceContext context) throws PluginException {
String interceptorDefineClassName = this.getClass().getName();
String transformClassName = typeDescription.getTypeName();
if (StringUtil.isEmpty(transformClassName)) {
LOGGER.warn("classname of being intercepted is not defined by {}.", interceptorDefineClassName);
return null;
}
LOGGER.debug("prepare to enhance class {} by {}.", transformClassName, interceptorDefineClassName);
/**
* find witness classes for enhance class
*/
String[] witnessClasses = witnessClasses();
if (witnessClasses != null) {
for (String witnessClass : witnessClasses) {
if (!WitnessClassFinder.INSTANCE.exist(witnessClass, classLoader)) {
LOGGER.warn("enhance class {} by plugin {} is not working. Because witness class {} is not existed.", transformClassName, interceptorDefineClassName, witnessClass);
return null;
}
}
}
DynamicType.Builder<?> newClassBuilder = this.enhance(typeDescription, builder, classLoader, context);
context.initializationStageCompleted();
LOGGER.debug("enhance class {} by {} completely.", transformClassName, interceptorDefineClassName);
return newClassBuilder;
}
protected abstract DynamicType.Builder<?> enhance(TypeDescription typeDescription,
DynamicType.Builder<?> newClassBuilder, ClassLoader classLoader, EnhanceContext context) throws PluginException;
protected abstract ClassMatch enhanceClass();
protected String[] witnessClasses() {
return new String[] {};
}
public boolean isBootstrapInstrumentation() {
return false;
}
public abstract ConstructorInterceptPoint[] getConstructorsInterceptPoints();
public abstract InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints();
public abstract StaticMethodsInterceptPoint[] getStaticMethodsInterceptPoints();
}
這個抽象基類中,最為重要的一個方法 define(),被premain中的transform呼叫,從而實現外掛中的攔截邏輯。
5、啟動服務
skywalking採用外掛架構,為了規範所有外掛,外掛都必須實現BootService這個介面,如下:
//BootService中宣告的生命週期方法,agent-core核心會在適當的時機呼叫不同的生命週期方法
public interface BootService {
void prepare() throws Throwable;
void boot() throws Throwable;
void onComplete() throws Throwable;
void shutdown() throws Throwable;
}
在premain方法的最後,會boot啟動服務:
try {
ServiceManager.INSTANCE.boot();
} catch (Exception e) {
LOGGER.error(e, "Skywalking agent boot failure.");
}
boot類是ServiceManager類下的方法,開啟boot方法:
public void boot() {
bootedServices = loadAllServices();
prepare();
startup();
onComplete();
}
<1>首先是loadAllService方法:
private Map<Class, BootService> loadAllServices() {
Map<Class, BootService> bootedServices = new LinkedHashMap<>();
List<BootService> allServices = new LinkedList<>();
load(allServices); //load方法,採用SPI機制實現
for (final BootService bootService : allServices) {
Class<? extends BootService> bootServiceClass = bootService.getClass();
//服務是否有註解@DefaultImplementor,預設實現
boolean isDefaultImplementor = bootServiceClass.isAnnotationPresent(DefaultImplementor.class);
if (isDefaultImplementor) {
if (!bootedServices.containsKey(bootServiceClass)) {
bootedServices.put(bootServiceClass, bootService);
} else {
//ignore the default service
}
} else {
//服務是否有註解@OverrideImplementor,覆蓋實現
OverrideImplementor overrideImplementor = bootServiceClass.getAnnotation(OverrideImplementor.class);
if (overrideImplementor == null) {
if (!bootedServices.containsKey(bootServiceClass)) {
bootedServices.put(bootServiceClass, bootService);
} else {
throw new ServiceConflictException("Duplicate service define for :" + bootServiceClass);
}
} else {
Class<? extends BootService> targetService = overrideImplementor.value();
if (bootedServices.containsKey(targetService)) {
boolean presentDefault = bootedServices.get(targetService)
.getClass()
.isAnnotationPresent(DefaultImplementor.class);
if (presentDefault) {
bootedServices.put(targetService, bootService);
} else {
throw new ServiceConflictException(
"Service " + bootServiceClass + " overrides conflict, " + "exist more than one service want to override :" + targetService);
}
} else {
bootedServices.put(targetService, bootService);
}
}
}
}
return bootedServices;
}
void load(List<BootService> allServices) { //load方法, ServiceLoader.load(BootService.class, AgentClassLoader.getDefault()))是典型的SPI機制
for (final BootService bootService : ServiceLoader.load(BootService.class, AgentClassLoader.getDefault())) {
allServices.add(bootService);
}
}
擴充套件:Java SPI思想梳理 - KKys的文章 - 知乎 https://zhuanlan.zhihu.com/p/28909673
6、最後一 步,註冊關閉鉤子
設定一個關閉鉤子,在JVM關閉時,建立一個執行緒,呼叫ServiceManager中的shutdown方法,進行資源釋放等。
Runtime.getRuntime()
.addShutdownHook(new Thread(ServiceManager.INSTANCE::shutdown, "skywalking service shutdown thread"));