java初学者指南_Java代理初学者指南
java初學者指南
盡管Java初學者很快學會了鍵入public static void main來運行他們的應用程序,但是即使是經驗豐富的開發人員也常常不知道JVM對Java流程的兩個附加入口點的支持: premain和agentmain方法。 這兩種方法都允許所謂的Java代理在駐留在其自己的jar文件中的同時對現有的Java程序做出貢獻,即使沒有被主應用程序顯式鏈接。 這樣做,有可能與托管它們的應用程序完全獨立地開發,發行和發布Java代理,同時仍在同一Java進程中運行它們。
最簡單的Java代理先于實際應用程序運行,例如執行一些動態設置。 代理可以例如安裝特定的SecurityManager或以編程方式配置系統屬性。 下面的類是一個不太有用的代理,但仍然可以作為一個很好的入門演示:在將控制權傳遞給實際應用程序的main方法之前,該類僅將一行打印到控制臺:
<pre class= "wp-block-syntaxhighlighter-code" >package sample; public class SimpleAgent<?> { public static void premain(String argument) { System.out.println( "Hello " + argument); } }< /pre >要將此類用作Java代理,需要將其包裝在jar文件中。 除常規Java程序外,無法從文件夾中加載Java代理的類。 另外,需要指定一個清單條目,該清單條目引用包含premain方法的類:
Premain-Class: sample.SimpleAgent通過此設置,現在可以在命令行上添加Java代理,方法是指向捆綁代理的文件系統位置,并可以選擇在等號后添加單個參數,如下所示:
java -javaagent:/location/of/agent.jar=世界some.random.Program
現在在some.random.Program執行main方法之前,將打印出Hello World ,其中第二個單詞是所提供的參數。
儀表API
如果搶占式代碼執行是Java代理的唯一功能,那么它們當然將沒有多大用處。 實際上,大多數Java代理僅是有用的,因為Java代理可以通過將類型為Instrumentation的第二個參數添加到代理的入口點方法來請求Java代理請求。 儀表API提供對JVM提供的較低級別功能的訪問,JVM是Java代理獨有的,而從未提供給常規Java程序。 工具API的核心是允許在Java類加載之前或之后對其進行修改。
任何已編譯的Java類都將存儲為.class文件,該文件在首次加載時將作為字節數組呈現給Java代理。 通過將一個或多個ClassFileTransformer注冊到檢測API來通知代理,該API會針對當前JVM進程的ClassLoader加載的任何類得到通知:
package sample; public class ClassLoadingAgent { public static void premain(String argument, Instrumentation instrumentation) { instrumentation.addTransformer(new ClassFileTransformer() { @Override public byte[] transform(Module module, ClassLoader loader, String name, Class<?> typeIfLoaded, ProtectionDomain domain, byte[] buffer) { System.out.println( "Class was loaded: " + name); return null; } }); } }在上面的示例中,代理通過從轉換器返回null來保持不運行狀態,這將終止轉換過程,但只會將帶有最新加載類的名稱的消息打印到控制臺。 但是,通過轉換buffer參數提供的字節數組,代理可以在加載任何類之前更改其行為。
轉換已編譯的Java類可能聽起來很復雜。 但是幸運的是, Java虛擬機規范(JVMS)詳細說明了代表類文件的每個字節的含義。 為了修改一種方法的行為,因此將識別該方法代碼的偏移量,然后向該方法添加所謂的Java字節代碼指令,以表示所需的已更改行為。 通常,這種轉換不是手動應用,而是通過使用字節碼處理器(最著名的是ASM庫)將類文件拆分成其組件來應用。 這樣,就可以孤立地查看字段,方法和注釋,從而可以應用更有針對性的轉換并節省一些記賬。
無干擾的代理
盡管ASM使類文件轉換更安全,更簡單,但它仍然依賴于庫用戶對字節碼及其特征的良好理解。 但是,其他通常基于ASM的庫允許在更高級別上表達字節碼轉換,這使得這種理解成為偶然。 此類庫的一個示例是Byte Buddy ,它由本文的作者開發和維護。 Byte Buddy旨在將字節碼轉換映射到大多數Java開發人員已經知道的概念,以使代理開發更容易進行。
為了編寫Java代理,Byte Buddy提供了AgentBuilder API,該API在ClassFileTransformer創建并注冊ClassFileTransformer 。 字節好友ClassFileTransformer直接注冊ClassFileTransformer ,而是允許指定ElementMatcher首先識別感興趣的類型。 對于每種匹配類型,然后可以指定一個或多個轉換。 然后,Byte Buddy將這些指令轉換為可以安裝到Instrumentation API中的轉換器的高性能實現。 例如,以下代碼在Byte Buddy的API中重新創建了先前的非運行轉換器:
package sample; public class ByteBuddySampleAgent { public static void premain(String argument, Instrumentation instrumentation) { new AgentBuilder.Default() . type (ElementMatchers.any()) .transform((DynamicType.Builder<?> builder, TypeDescription type , ClassLoader loader, JavaModule module) -> { System.out.println( "Class was loaded: " + name); return builder; }).installOn(instrumentation); } }應該提到的是,與前面的示例相反,Byte Buddy將轉換所有發現的類型,而無需應用更改,而效率的降低則是完全忽略那些不需要的類型。 另外,如果沒有另外指定,默認情況下它將忽略Java核心庫的類。 但是實質上,可以實現相同的效果,從而可以使用上述代碼演示使用Byte Buddy的簡單代理。
使用Byte Buddy建議測量執行時間
字節伙伴不是將類文件公開為字節數組,而是嘗試將常規Java代碼編織或鏈接到已檢測類中。 這樣,Java代理的開發人員無需直接生成字節碼,而可以依賴于Java編程語言及其已與之建立關系的現有工具。 對于使用Byte Buddy編寫的Java代理,行為通常由建議類表示,其中帶注釋的方法描述了添加到現有方法的開頭和結尾的行為。 例如,以下建議類用作模板,該模板將方法的執行時間打印到控制臺:
public class TimeMeasurementAdvice { @Advice.OnMethodEnter public static long enter() { return System.currentTimeMillis(); } @Advice.OnMethodExit(onThrowable = Throwable.class) public static void exit (@Advice.Enter long start, @Advice.Origin String origin) { long executionTime = System.currentTimeMillis() - start; System.out.println(origin + " took " + executionTime + " to execute" ); } }在上面的建議類中,enter方法僅記錄當前時間戳,并返回該時間戳以使其在方法末尾可用。 如圖所示,輸入建議在實際方法主體之前執行。 在方法結束時,將應用退出建議,在該建議中,將從當前時間戳中減去所記錄的值,以確定該方法的執行時間。 然后將執行時間打印到控制臺。
為了利用建議,需要將其應用在先前示例中仍未運行的變壓器中。 為避免打印任何方法的運行時,我們將建議的應用程序條件MeasureTime自定義的,保留了運行時的注釋MeasureTime ,應用程序開發人員可以將其添加到其類中。
package sample; public class ByteBuddyTimeMeasuringAgent { public static void premain(String argument, Instrumentation instrumentation) { Advice advice = Advice.to(TimeMeasurementAdvice.class); new AgentBuilder.Default() . type (ElementMatchers.isAnnotatedBy(MeasureTime.class)) .transform((DynamicType.Builder<?> builder, TypeDescription type , ClassLoader loader, JavaModule module) -> { return builder.visit(advice.on(ElementMatchers.isMethod()); }).installOn(instrumentation); } }給定上述代理程序的應用程序之后,如果通過MeasureTime注釋了一個類,則現在將所有方法執行時間打印到控制臺。 實際上,以更結構化的方式收集此類指標當然更有意義,但是在已經完成打印輸出之后,這不再是一項復雜的任務。
動態代理附件和類重新定義
在Java 8之前,這要歸功于JDK的tools.jar中存儲的實用程序,該實用程序可以在JDK的安裝文件夾中找到。 從Java 9開始,此jar已分解到jdk.attach模塊中,該模塊現在可在任何常規JDK發行版中使用。 使用包含的工具API,可以使用以下代碼將JAR文件附加到具有給定進程ID的JVM:
VirtualMachine vm = VirtualMachine.attach(processId); try { vm.loadAgent( "/location/of/agent.jar" ); } finally { vm.detach(); }調用上述API時,JVM將使用給定的ID定位進程,并在該遠程虛擬機內的專用線程中執行agent agentmain方法。 此外,此類代理可能會要求有權在其清單中重新轉換類,以更改已加載的類的代碼:
Agentmain-Class: sample.SimpleAgent Can-Retransform-Classes: true給定這些清單條目后,代理現在可以請求考慮將任何已加載的類進行重新轉換, ClassFileTransformer可以使用附加的布爾參數來注冊先前的ClassFileTransformer ,這表明需要在重新轉換嘗試時得到通知:
package sample; public class ClassReloadingAgent { public static void agentmain(String argument, Instrumentation instrumentation) { instrumentation.addTransformer(new ClassFileTransformer() { @Override public byte[] transform(Module module, ClassLoader loader, String name, Class<?> typeIfLoaded, ProtectionDomain domain, byte[] buffer) { if (typeIfLoaded == null) { System.out.println( "Class was loaded: " + name); } else { System.out.println( "Class was re-loaded: " + name); } return null; } }, true ); instrumentation.retransformClasses( instrumentation.getAllLoadedClasses()); } }為了指示已經加載了一個類,現在將已加載類的實例提供給轉換器,對于之前未加載的類,該實例為null 。 在以上示例的末尾,請求儀表API獲取所有已加載的類,以提交任何此類類進行重新轉換,從而觸發轉換器的執行。 和以前一樣,出于演示工具API的目的,將類文件轉換器實現為不可操作。
當然,Byte Buddy還通過注冊重新轉換策略在其API中涵蓋了這種轉換形式,在這種情況下,Byte Buddy還將考慮所有要進行轉換的類。 這樣做,可以調整先前的時間測量代理程序,使其在動態附加的情況下也考慮加載的類:
package sample; public class ByteBuddyTimeMeasuringRetransformingAgent { public static void agentmain(String argument, Instrumentation instrumentation) { Advice advice = Advice.to(TimeMeasurementAdvice.class); new AgentBuilder.Default() .with(AgentBuilder.RetransformationStrategy.RETRANSFORMATION) .disableClassFormatChanges() . type (ElementMatchers.isAnnotatedBy(MeasureTime.class)) .transform((DynamicType.Builder<?> builder, TypeDescription type , ClassLoader loader, JavaModule module) -> { return builder.visit(advice.on(ElementMatchers.isMethod()); }).installOn(instrumentation); } }為了最終方便,Byte Buddy還提供了一個用于附加到JVM的API,該API對JVM版本和供應商進行了抽象,以使附加過程盡可能地簡單。 給定一個進程ID,Byte Buddy可以通過執行一行代碼將代理附加到JVM:
ByteBuddyAgent.attach(processId, "/location/of/agent.jar" );此外,甚至可以將當前正在運行的同一虛擬機進程附加到測試代理時特別方便的進程:
Instrumentation instrumentation = ByteBuddyAgent. install ();此功能可作為其自己的工件byte-buddy-agent使用 ,由于使用Instrumentation實例可以直接(例如,從一個單元中直接調用premain或agentmain方法)成為可能,因此自己嘗試嘗試自定義代理很簡單。測試,無需任何其他設置。
翻譯自: https://www.javacodegeeks.com/2019/12/a-beginners-guide-to-java-agents.html
java初學者指南
總結
以上是生活随笔為你收集整理的java初学者指南_Java代理初学者指南的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用文本编辑器和jdk_JDK 14:记
- 下一篇: 你不来我不老歌词 你不来我不老歌曲简介