skywalking源碼解析javaAgent工具ByteBuddy應(yīng)用
前言
關(guān)于skywalking請(qǐng)看我上一篇博文,skywalking分布式服務(wù)調(diào)用鏈路追蹤APM應(yīng)用監(jiān)控 其使用javaAgent技術(shù),使得應(yīng)用接入監(jiān)控0耦合。今天在分析skywaking過(guò)程中,對(duì)javaAgent技術(shù)有了更深入的了解。skywalking使用的javaAgent工具ByteBuddy是一個(gè)比ASM更上層的針對(duì)java字節(jié)碼操作的封裝,基于ByteBuddy,我們可以快速方便的對(duì)java字節(jié)碼進(jìn)行增強(qiáng)處理,更高效的開發(fā)javaAgent應(yīng)用。
Byte Buddy官網(wǎng):https://bytebuddy.net/#/
github項(xiàng)目地址:https://github.com/raphw/byte-buddy
本文共分三個(gè)部分,分別為skywalking的agent模塊源碼分析,javaAgent技術(shù)應(yīng)用,ByteBuddy工具應(yīng)用
Agent模塊源碼分析
agent的入口方法premain在apm-sniffer模塊的SkyWalkingAgent類中,整個(gè)agent邏輯如下:
第一步,加載配置信息:
加載Agentjar包所在錄入的/config/agent.config文件,參數(shù)加載的優(yōu)先級(jí)分別為:系統(tǒng)環(huán)境變量 > VM參數(shù)(-D) > /config/agent.config中的配置。所以Agent安裝包的目錄別輕易改動(dòng),相關(guān)的讀取配置在代碼里寫死了的
第二步,加載需要被Agent的插件:
插件代碼在apm-sck-plugin模塊下,目前共有24個(gè)插件支持,包含主流Rpc如(dubbo,motan,grpc)等,下面以dubbo的Agent插件列,看skywalking如何開發(fā)相關(guān)套件的。
分兩步:
- 實(shí)現(xiàn)InstanceMethodsAroundInterceptor接口,實(shí)現(xiàn)beforeMethod和afterMethod方法,環(huán)繞增強(qiáng)目標(biāo)方法,如rpc和http的請(qǐng)求等
- 定義需要攔截的類和增強(qiáng)的方法,繼承ClassInstanceMethodsEnhancePluginDefine,dubbo插件增強(qiáng)的是監(jiān)控的MonitorFilter類中的invoke方法,所以如果你的應(yīng)用沒有開啟數(shù)據(jù)監(jiān)控服務(wù),skywalking是收集不到dubbo的調(diào)用數(shù)據(jù)的。選擇增強(qiáng)monitorfilter可能也是為了考量加入agent的性能問(wèn)題。代碼如下:
public class DubboInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
private static final String ENHANCE_CLASS = "com.alibaba.dubbo.monitor.support.MonitorFilter";
private static final String INTERCEPT_CLASS = "org.skywalking.apm.plugin.dubbo.DubboInterceptor";
@Override
protected ClassMatch enhanceClass() {
return byName(ENHANCE_CLASS);
}
@Override
protected ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
return null;
}
@Override
protected InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
return new InstanceMethodsInterceptPoint[] {
new InstanceMethodsInterceptPoint() {
@Override
public ElementMatchergetMethodsMatcher() {
return named("invoke");
}
@Override
public String getMethodsInterceptor() {
return INTERCEPT_CLASS;
}
@Override
public boolean isOverrideArgs() {
return false;
}
}
};
}
}第三步,加載Agent端所需要的服務(wù):
通過(guò)java的spi機(jī)制ServiceLoader.load(BootService.class)加載agen端的所需服務(wù),Agent端共有七個(gè)基礎(chǔ)service服務(wù),分別如下
- AppAndServiceRegisterClient:服務(wù)注冊(cè)客戶端服務(wù)
- CollectorDiscoveryService:連接通訊發(fā)現(xiàn)服務(wù)
- ContextManager:trace等上下文管理服務(wù),在業(yè)務(wù)應(yīng)用代碼中,可在此服務(wù)中獲取到當(dāng)前的traceId等信息
- GRPCChannelManager:grpc通訊連接通道管理服務(wù)
- JVMService:jvm監(jiān)控信息收集服務(wù),主要收集cpu,內(nèi)存等信息
- SamplingService:數(shù)據(jù)取樣服務(wù),可配置,默認(rèn)為-1,將所有數(shù)據(jù)發(fā)送到收集器。skywalking考慮到序列化/反序列化的CPU成本和網(wǎng)絡(luò)帶寬可以設(shè)置為不將所有采樣數(shù)據(jù)發(fā)送到收集器。
- TraceSegmentServiceClient:trace和span信息組裝客戶端服務(wù)
第四步,使用ByteBuddy增強(qiáng)插件定義的所有class:
代碼如下
new AgentBuilder.Default().type(pluginFinder.buildMatch()).transform(new AgentBuilder.Transformer() {
@Override
public DynamicType.Builder transform(DynamicType.Builder builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) {
ListpluginDefines = pluginFinder.find(typeDescription, classLoader);
if (pluginDefines.size() > 0) {
DynamicType.Builder newBuilder = builder;
EnhanceContext context = new EnhanceContext();
for (AbstractClassEnhancePluginDefine define : pluginDefines) {
DynamicType.Builder possibleNewBuilder = define.define(typeDescription.getTypeName(), 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;
}
}).with(new AgentBuilder.Listener() {
@Override
public void onDiscovery(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) {
}
@Override
public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module,
boolean loaded, DynamicType dynamicType) {
if (logger.isDebugEnable()) {
logger.debug("On Transformation class {}.", typeDescription.getName());
}
}
@Override
public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module,
boolean loaded) {
}
@Override public void onError(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded,
Throwable throwable) {
logger.error("Failed to enhance class " + typeName, throwable);
}
@Override
public void onComplete(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) {
}
}).installOn(instrumentation);使用ByteBuddy代碼字節(jié)碼增強(qiáng)特別簡(jiǎn)單,開發(fā)agent應(yīng)用不用再操作instrumentation的相關(guān)接口了
javaAgent的應(yīng)用
Java agent是在另外一個(gè)Java應(yīng)用(“目標(biāo)”應(yīng)用)啟動(dòng)之前要執(zhí)行的Java程序,這樣agent就有機(jī)會(huì)修改目標(biāo)應(yīng)用或者應(yīng)用所運(yùn)行的環(huán)境。在本文中,我們將會(huì)從基礎(chǔ)內(nèi)容開始,逐漸增強(qiáng)其功能,借助字節(jié)碼操作工具Byte Buddy,使其成為高級(jí)的agent實(shí)現(xiàn)。
在最基本的用例中,Java agent會(huì)用來(lái)設(shè)置應(yīng)用屬性或者配置特定的環(huán)境狀態(tài),agent能夠作為可重用和可插入的組件。如下的樣例描述了這樣的一個(gè)agent,它設(shè)置了一個(gè)系統(tǒng)屬性,在實(shí)際的程序中就可以使用該屬性了:
public class Agent {
public static void premain(String arg) {
System.setProperty("my-property", “foo”);
}
}如上面的代碼所述,Java agent的定義與其他的Java程序類似,只不過(guò)它使用premain方法替代main方法作為入口點(diǎn)。顧名思義,這個(gè)方法能夠在目標(biāo)應(yīng)用的main方法之前執(zhí)行。相對(duì)于其他的Java程序,編寫agent并沒有特定的規(guī)則。有一個(gè)很小的區(qū)別在于,Java agent接受一個(gè)可選的參數(shù),而不是包含零個(gè)或更多參數(shù)的數(shù)組。
如果要使用這個(gè)agent,必須要將agent類和資源打包到j(luò)ar中,并且在jar的manifest中要將Agent-Class屬性設(shè)置為包含premain方法的agent類。(agent必須要打包到j(luò)ar文件中,它不能通過(guò)拆解的格式進(jìn)行指定。)接下來(lái),我們需要啟動(dòng)應(yīng)用程序,并且在命令行中通過(guò)javaagent參數(shù)來(lái)引用jar文件的位置:
java -javaagent:myAgent.jar -jar myProgram.jar
通過(guò)重復(fù)使用javaagent命令,能夠添加多個(gè)agent。
但是,Java agent的功能并不局限于修改應(yīng)用程序環(huán)境的狀態(tài),Java agent能夠訪問(wèn)Java instrumentation API,這樣的話,agent就能修改目標(biāo)應(yīng)用程序的代碼。Java虛擬機(jī)中這個(gè)鮮為人知的特性提供了一個(gè)強(qiáng)大的工具,有助于實(shí)現(xiàn)面向切面的編程。
如果要對(duì)Java程序進(jìn)行這種修改,我們需要在agent的premain方法上添加類型為Instrumentation的第二個(gè)參數(shù)。Instrumentation參數(shù)可以用來(lái)執(zhí)行一系列的任務(wù),比如確定對(duì)象以字節(jié)為單位的精確大小以及通過(guò)注冊(cè)ClassFileTransformers實(shí)際修改類的實(shí)現(xiàn)。ClassFileTransformers注冊(cè)之后,當(dāng)類加載器(class loader)加載類的時(shí)候都會(huì)調(diào)用它。當(dāng)它被調(diào)用時(shí),在類文件所代表的類加載之前,類文件transformer有機(jī)會(huì)改變或完全替換這個(gè)類文件。按照這種方式,在類使用之前,我們能夠增強(qiáng)或修改類的行為,如下面的樣例所示:
public class Agent {
public static void premain(String argument, Instrumentation inst) {
inst.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(
ClassLoader loader,
String className,
Class classBeingRedefined, // 如果類之前沒有加載的話,值為null
ProtectionDomain protectionDomain,
byte[] classFileBuffer) {
// 返回改變后的類文件。
}
});
}
}通過(guò)使用Instrumentation實(shí)例注冊(cè)上述的ClassFileTransformer之后,每個(gè)類加載的時(shí)候,都會(huì)調(diào)用這個(gè)transformer。為了實(shí)現(xiàn)這一點(diǎn),transformer會(huì)接受一個(gè)二進(jìn)制和類加載器的引用,分別代表了類文件以及試圖加載類的類加載器。
Java agent也可以在Java應(yīng)用的運(yùn)行期注冊(cè),如果是在這種場(chǎng)景下,instrumentation API允許重新定義已加載的類,這個(gè)特性被稱之為“HotSwap”。不過(guò),重新定義類僅限于替換方法體。在重新定義類的時(shí)候,不能新增或移除類成員,并且類型和簽名也不能進(jìn)行修改。當(dāng)類第一次加載的時(shí)候,并沒有這種限制,如果是在這樣的場(chǎng)景下,那classBeingRedefined會(huì)被設(shè)置為null。
BYTE BUDDY應(yīng)用
Byte Buddy的目的并不僅僅是為了生成Java agent。它提供了一個(gè)API用于生成任意的Java類,基于這個(gè)生成類的API,Byte Buddy提供了額外的API來(lái)生成Java agent。
作為Byte Buddy的簡(jiǎn)介,如下的樣例展現(xiàn)了如何生成一個(gè)簡(jiǎn)單的類,這個(gè)類是Object的子類,并且重寫了toString方法,用來(lái)返回“Hello World!”。與原始的ASM類似,“intercept”會(huì)告訴Byte Buddy為攔截到的指令提供方法實(shí)現(xiàn):
Class dynamicType = new ByteBuddy()
.subclass(Object.class)
.method(ElementMatchers.named("toString"))
.intercept(FixedValue.value("Hello World!"))
.make()
.load(getClass().getClassLoader(),
ClassLoadingStrategy.Default.WRAPPER)
.getLoaded();從上面的代碼中,我們可以看到Byte Buddy要實(shí)現(xiàn)一個(gè)方法分為兩步。首先,編程人員需要指定一個(gè)ElementMatcher,它負(fù)責(zé)識(shí)別一個(gè)或多個(gè)需要實(shí)現(xiàn)的方法。Byte Buddy提供了功能豐富的預(yù)定義攔截器(interceptor),它們暴露在ElementMatchers類中。在上述的例子中,toString方法完全精確匹配了名稱,但是,我們也可以匹配更為復(fù)雜的代碼結(jié)構(gòu),如類型或注解。
當(dāng)Byte Buddy生成類的時(shí)候,它會(huì)分析所生成類型的類層級(jí)結(jié)構(gòu)。在上述的例子中,Byte Buddy能夠確定所生成的類要繼承其超類Object的名為toString的方法,指定的匹配器會(huì)要求Byte Buddy重寫該方法,這是通過(guò)隨后的 Implementation 實(shí)例實(shí)現(xiàn)的,在我們的樣例中,這個(gè)實(shí)例也就是FixedValue。
當(dāng)創(chuàng)建子類的時(shí)候,Byte Buddy始終會(huì)攔截(intercept)一個(gè)匹配的方法,在生成的類中重寫該方法。但是,我們?cè)诒疚纳院髮?huì)看到Byte Buddy還能夠重新定義已有的類,而不必通過(guò)子類的方式來(lái)實(shí)現(xiàn)。在這種情況下,Byte Buddy會(huì)將已有的代碼替換為生成的代碼,而將原有的代碼復(fù)制到另外一個(gè)合成的(synthetic)方法中。
在我們上面的代碼樣例中,匹配的方法進(jìn)行了重寫,在實(shí)現(xiàn)里面,返回了固定的值“Hello World!”。intercept方法接受Implementation類型的參數(shù),Byte Buddy自帶了多個(gè)預(yù)先定義的實(shí)現(xiàn),如上文所使用的FixedValue類。但是,如果需要的話,可以使用前文所述的ASM API將某個(gè)方法實(shí)現(xiàn)為自定義的字節(jié)碼,Byte Buddy本身也是基于ASM API實(shí)現(xiàn)的。
定義完類的屬性之后,就能通過(guò)make方法來(lái)進(jìn)行生成。在樣例應(yīng)用中,因?yàn)橛脩魶]有指定類名,所以生成的類會(huì)給定一個(gè)任意的名稱。最終,生成的類將會(huì)使用ClassLoadingStrategy來(lái)進(jìn)行加載。通過(guò)使用上述的默認(rèn) WRAPPER策略,類將會(huì)使用一個(gè)新的類加載器進(jìn)行加載,這個(gè)類加載器會(huì)使用環(huán)境類加載器作為父加載器。
類加載之后,使用Java反射API就可以訪問(wèn)它了。如果沒有指定其他構(gòu)造器的話,Byte Buddy將會(huì)生成類似于父類的構(gòu)造器,因此生成的類可以使用默認(rèn)的構(gòu)造器。這樣,我們就可以檢驗(yàn)生成的類重寫了 toString方法,如下面的代碼所示:
assertThat(dynamicType.newInstance().toString(),
is("Hello World!"));當(dāng)然,這個(gè)生成的類并沒有太大的用處。對(duì)于實(shí)際的應(yīng)用來(lái)講,大多數(shù)方法的返回值是在運(yùn)行時(shí)計(jì)算的,這個(gè)計(jì)算過(guò)程要依賴于方法的參數(shù)和對(duì)象的狀態(tài)。
通過(guò)委托實(shí)現(xiàn)Instrumentation
要實(shí)現(xiàn)某個(gè)方法,有一種更為靈活的方式,那就是使用Byte Buddy的MethodDelegation。通過(guò)使用方法委托,在生成重寫的實(shí)現(xiàn)時(shí),我們就有可能調(diào)用給定類和實(shí)例的其他方法。按照這種方式,我們可以使用如下的委托器(delegator)重新編寫上述的樣例:
class ToStringInterceptor {
static String intercept() {
return “Hello World!”;
}
}借助上面的POJO攔截器,我們就可以將之前的FixedValue實(shí)現(xiàn)替換為MethodDelegation.to(ToStringInterceptor.class):
Class dynamicType = new ByteBuddy()
.subclass(Object.class)
.method(ElementMatchers.named("toString"))
.intercept(MethodDelegation.to(ToStringInterceptor.class))
.make()
.load(getClass().getClassLoader(),
ClassLoadingStrategy.Default.WRAPPER)
.getLoaded();使用上述的委托器,Byte Buddy會(huì)在to方法所給定的攔截目標(biāo)中,確定最優(yōu)的調(diào)用方法。就ToStringInterceptor.class來(lái)講,選擇過(guò)程只是非常簡(jiǎn)單地解析這個(gè)類型的唯一靜態(tài)方法而已。在本例中,只會(huì)考慮一個(gè)靜態(tài)方法,因?yàn)槲械哪繕?biāo)中指定的是一個(gè)類。與之不同的是,我們還可以將其委托給某個(gè)類的實(shí)例,如果是這樣的話,Byte Buddy將會(huì)考慮所有的虛方法(virtual method)。如果類或?qū)嵗嫌卸鄠€(gè)這樣的方法,那么Byte Buddy首先會(huì)排除掉所有與指定instrumentation不兼容的方法。在剩余的方法中,庫(kù)將會(huì)選擇最佳的匹配者,通常來(lái)講這會(huì)是參數(shù)最多的方法。我們還可以顯式地指定目標(biāo)方法,這需要縮小合法方法的范圍,將ElementMatcher傳遞到MethodDelegation中,就會(huì)進(jìn)行方法的過(guò)濾。例如,通過(guò)添加如下的filter,Byte Buddy只會(huì)將名為“intercept”的方法視為委托目標(biāo):
MethodDelegation.to(ToStringInterceptor.class)
.filter(ElementMatchers.named(“intercept”))執(zhí)行上面的攔截之后,被攔截到的方法依然會(huì)打印出“Hello World!”,但是這次的結(jié)果是動(dòng)態(tài)計(jì)算的,這樣的話,我們就可以在攔截器方法上設(shè)置斷點(diǎn),所生成的類每次調(diào)用toString時(shí),都會(huì)觸發(fā)攔截器的方法。
當(dāng)我們?yōu)閿r截器方法設(shè)置參數(shù)時(shí),就能釋放出MethodDelegation的全部威力。這里的參數(shù)通常是帶有注解的,用來(lái)要求Byte Buddy在調(diào)用攔截器方法時(shí),注入某個(gè)特定的值。例如,通過(guò)使用@Origin注解,Byte Buddy提供了添加instrument功能的方法的實(shí)例,將其作為Java反射API中類的實(shí)例:
class ContextualToStringInterceptor {
static String intercept(@Origin Method m) {
return “Hello World from ” + m.getName() + “!”;
}
}當(dāng)攔截toString方法時(shí),對(duì)instrument方法的調(diào)用將會(huì)返回“Hello world from toString!”。
除了@Origin注解以外,Byte Buddy提供了一組功能豐富的注解。例如,通過(guò)在類型為Callable的參數(shù)上使用@Super注解,Byte Buddy會(huì)創(chuàng)建并注入一個(gè)代理實(shí)例,它能夠調(diào)用被instrument方法的原始代碼。如果對(duì)于特定的用戶場(chǎng)景,所提供的注解不能滿足需求或者不太適合的話,我們甚至能夠注冊(cè)自定義的注解,讓這些注解注入用戶特定的值。
實(shí)現(xiàn)方法級(jí)別的安全性
可以看到,我們?cè)谶\(yùn)行時(shí)可以借助簡(jiǎn)單的Java代碼,使用MethodDelegation來(lái)動(dòng)態(tài)重寫某個(gè)方法。這只是一個(gè)簡(jiǎn)單的樣例,但是這項(xiàng)技術(shù)可以用到更加實(shí)際的應(yīng)用之中。在本文剩余的內(nèi)容中,我們將會(huì)開發(fā)一個(gè)樣例,它會(huì)使用代碼生成技術(shù)實(shí)現(xiàn)一個(gè)注解驅(qū)動(dòng)的庫(kù),用來(lái)限制方法級(jí)別的安全性。在我們的第一個(gè)迭代中,這個(gè)庫(kù)會(huì)通過(guò)生成子類的方式來(lái)限制安全性。然后,我們將會(huì)采取相同的方式來(lái)實(shí)現(xiàn)Java agent,完成相同的功能。
樣例庫(kù)會(huì)使用如下的注解,允許用戶指定某個(gè)方法需要考慮安全因素:
@interface Secured {
String user();
}例如,假設(shè)應(yīng)用需要使用如下的Service類來(lái)執(zhí)行敏感操作,并且只有用戶被認(rèn)證為管理員才能執(zhí)行該方法。這是通過(guò)為執(zhí)行這個(gè)操作的方法聲明Secured注解來(lái)指定的:
class Service {
@Secured(user = “ADMIN”)
void doSensitiveAction() {
// 運(yùn)行敏感代碼...
}
}我們當(dāng)然可以將安全檢查直接編寫到方法中。在實(shí)際中,硬編碼橫切關(guān)注點(diǎn)往往會(huì)導(dǎo)致復(fù)制-粘貼的邏輯,使其難以維護(hù)。另外,一旦應(yīng)用需要涉及額外的需求時(shí),如日志、收集調(diào)用指標(biāo)或結(jié)果緩存,直接添加這樣的代碼擴(kuò)展性不會(huì)很好。通過(guò)將這樣的功能抽取到agent中,方法就能很純粹地關(guān)注其業(yè)務(wù)邏輯,使得代碼庫(kù)能夠更易于閱讀、測(cè)試和維護(hù)。
為了讓我們規(guī)劃的庫(kù)保持盡可能得簡(jiǎn)單,按照注解的協(xié)議聲明,如果當(dāng)前用戶不具備注解的用戶屬性時(shí),將會(huì)拋出IllegalStateException異常。通過(guò)使用Byte Buddy,這種行為可以用一個(gè)簡(jiǎn)單的攔截器來(lái)實(shí)現(xiàn),如下面樣例中的SecurityInterceptor所示,它會(huì)通過(guò)其靜態(tài)的user域,跟蹤當(dāng)前用戶已經(jīng)進(jìn)行了登錄:
class SecurityInterceptor {
static String user = “ANONYMOUS”
static void intercept(@Origin Method method) {
if (!method.getAnnotation(Secured.class).user().equals(user)) {
throw new IllegalStateException(“Wrong user”);
}
}
}通過(guò)上面的代碼,我們可以看到,即便給定用戶授予了訪問(wèn)權(quán)限,攔截器也沒有調(diào)用原始的方法。為了解決這個(gè)問(wèn)題,Byte Buddy有很多預(yù)定義的方法可以實(shí)現(xiàn)功能的鏈接。借助MethodDelegation類的andThen方法,上述的安全檢查可以放到原始方法的調(diào)用之前,如下面的代碼所示。如果用戶沒有進(jìn)行認(rèn)證的話,安全檢查將會(huì)拋出異常并阻止后續(xù)的執(zhí)行,因此原始方法將不會(huì)執(zhí)行。
將這些功能集合在一起,我們就能生成Service的一個(gè)子類,所有帶有注解方法的都能恰當(dāng)?shù)剡M(jìn)行安全保護(hù)。因?yàn)樗傻念愂荢ervice的子類,所以它能夠替代所有類型為Service的變量,并不需要任何的類型轉(zhuǎn)換,如果沒有恰當(dāng)認(rèn)證的話,調(diào)用doSensitiveAction方法就會(huì)拋出異常:
new ByteBuddy()
.subclass(Service.class)
.method(ElementMatchers.isAnnotatedBy(Secured.class))
.intercept(MethodDelegation.to(SecurityInterceptor.class)
.andThen(SuperMethodCall.INSTANCE)))
.make()
.load(getClass().getClassLoader(),
ClassLoadingStrategy.Default.WRAPPER)
.getLoaded()
.newInstance()
.doSensitiveAction();不過(guò)壞消息是,因?yàn)閷?shí)現(xiàn)instrumentation功能的子類是在運(yùn)行時(shí)創(chuàng)建的,所以除了使用Java反射以外,沒有其他辦法創(chuàng)建這樣的實(shí)例。因此,所有instrumentation類的實(shí)例都應(yīng)該通過(guò)一個(gè)工廠來(lái)創(chuàng)建,這個(gè)工廠會(huì)封裝創(chuàng)建instrumentation子類的復(fù)雜性。這樣造成的結(jié)果就是,子類instrumentation通常會(huì)用于框架之中,這些框架本身就需要通過(guò)工廠來(lái)創(chuàng)建實(shí)例,例如,像依賴管理的框架Spring或?qū)ο?關(guān)系映射的框架Hibernate,而對(duì)于其他類型的應(yīng)用來(lái)講,子類instrumentation實(shí)現(xiàn)起來(lái)通常過(guò)于復(fù)雜。
實(shí)現(xiàn)安全功能的JAVA AGENT
通過(guò)使用Java agent,上述安全框架的一個(gè)替代實(shí)現(xiàn)將會(huì)修改Service類的原始字節(jié)碼,而不是重寫它。這樣做的話,我們就沒有必要?jiǎng)?chuàng)建托管的實(shí)例了,只需簡(jiǎn)單地調(diào)用
new Service().doSensitiveAction()
即可,如果對(duì)應(yīng)的用戶沒有進(jìn)行認(rèn)證的話,就會(huì)拋出異常。為了支持這種方式,Byte Buddy提供一種稱之為rebase某個(gè)類的理念。當(dāng)rebase某個(gè)類的時(shí)候,不會(huì)創(chuàng)建子類,所采用的策略是實(shí)現(xiàn)instrumentation功能的代碼將會(huì)合并到被instrument的類中,從而改變其行為。在添加instrumentation功能之后,在被instrument的類中,其所有方法的原始代碼均可進(jìn)行訪問(wèn),因此像SuperMethodCall這樣的instrumentation,工作方式與創(chuàng)建子類是完全一樣的。
創(chuàng)建子類與rebase的行為是非常類似的,所以兩種操作的API執(zhí)行方式是一致的,都會(huì)使用相同的DynamicType.Builder接口來(lái)描述某個(gè)類型。兩種形式的instrumentation都可以通過(guò)ByteBuddy類來(lái)進(jìn)行訪問(wèn)。為了使Java agent的定義更加便利,Byte Buddy還提供了 AgentBuilder類,它希望能夠以一種簡(jiǎn)潔的方式應(yīng)對(duì)一些通用的用戶場(chǎng)景。為了定義Java agent實(shí)現(xiàn)方法級(jí)別的安全性,將如下的類定義為agent的入口點(diǎn)就足以完成該功能了:
class SecurityAgent {
public static void premain(String arg, Instrumentation inst) {
new AgentBuilder.Default()
.type(ElementMatchers.any())
.transform((builder, type) -> builder
.method(ElementMatchers.isAnnotatedBy(Secured.class)
.intercept(MethodDelegation.to(SecurityInterceptor.class)
.andThen(SuperMethodCall.INSTANCE))))
.installOn(inst);
}
}如果將這個(gè)agent打包為jar文件并在命令行中進(jìn)行指定,那么所有帶有Secured注解的方法將會(huì)進(jìn)行“轉(zhuǎn)換”或重定義,從而實(shí)現(xiàn)安全保護(hù)。如果不激活這個(gè)Java agent的話,應(yīng)用在運(yùn)行時(shí)就不包含額外的安全檢查。當(dāng)然,這意味著如果對(duì)帶有注解的代碼進(jìn)行單元測(cè)試的話,這些方法的調(diào)用并不需要特殊的搭建過(guò)程來(lái)模擬安全上下文。Java運(yùn)行時(shí)會(huì)忽略掉無(wú)法在classpath中找到的注解類型,因此在運(yùn)行帶有注解的方法時(shí),我們甚至完全可以在應(yīng)用中移除掉安全庫(kù)。
另外一項(xiàng)優(yōu)勢(shì)在于,Java agent能夠很容易地進(jìn)行疊加。如果在命令行中指定多個(gè)Java agent的話,每個(gè)agent都有機(jī)會(huì)對(duì)類進(jìn)行修改,其順序就是在命令行中所指定的順序。例如,我們可以采取這種方式將安全、日志以及監(jiān)控框架聯(lián)合在一起,而不需要在這些應(yīng)用間增添任何形式的集成層。因此,使用Java agent實(shí)現(xiàn)橫切的關(guān)注點(diǎn)提供了一種更為模塊化的代碼編寫方式,而不必針對(duì)某個(gè)管理實(shí)例的中心框架來(lái)集成所有的代碼。
特別說(shuō)明:ByteBuddy部分節(jié)選Rafael Winterhalter的《Easily Create Java Agents with Byte Buddy》
譯文地址:http://www.dbjr.com.cn/article/239718.htm
以上就是skywalking源碼解析javaAgent工具ByteBuddy應(yīng)用的詳細(xì)內(nèi)容,更多關(guān)于skywalking源碼解析javaAgent ByteBuddy的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot監(jiān)聽器的實(shí)現(xiàn)示例
在SpringBoot中,你可以使用監(jiān)聽器來(lái)響應(yīng)特定的事件,本文主要介紹了SpringBoot監(jiān)聽器的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下2023-12-12
Java使用正則表達(dá)式判斷獨(dú)立字符的存在(代碼示例)
通過(guò)使用正則表達(dá)式,我們可以更加靈活地判斷字符串中是否包含特定的字符,并且可以控制匹配的條件,如獨(dú)立的字符,這為我們處理字符串提供了更多的選擇和功能,這篇文章主要介紹了Java使用正則表達(dá)式判斷獨(dú)立字符的存在,需要的朋友可以參考下2023-10-10
Spring Cloud超詳細(xì)i講解Feign自定義配置與使用
這篇文章主要介紹了SpringCloud Feign自定義配置與使用,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-06-06
SpringBoot實(shí)現(xiàn)動(dòng)態(tài)多線程并發(fā)定時(shí)任務(wù)
這篇文章主要為大家詳細(xì)介紹了SpringBoot實(shí)現(xiàn)動(dòng)態(tài)多線程并發(fā)定時(shí)任務(wù),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-05-05
springboot的SpringPropertyAction事務(wù)屬性源碼解讀
這篇文章主要介紹了springboot的SpringPropertyAction事務(wù)屬性源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11
Spring開發(fā)核心之AOP的實(shí)現(xiàn)與切入點(diǎn)持久化
面向?qū)ο缶幊淌且环N編程方式,此編程方式的落地需要使用“類”和 “對(duì)象”來(lái)實(shí)現(xiàn),所以,面向?qū)ο缶幊唐鋵?shí)就是對(duì) “類”和“對(duì)象” 的使用,面向切面編程,簡(jiǎn)單的說(shuō),就是動(dòng)態(tài)地將代碼切入到類的指定方法、指定位置上的編程思想就是面向切面的編程2022-10-10

