java代理模式_Java代理
java代理模式
本文是我們名為“ 高級Java ”的學院課程的一部分。
本課程旨在幫助您最有效地使用Java。 它討論了高級主題,包括對象創建,并發,序列化,反射等。 它將指導您完成Java掌握的旅程! 在這里查看 !
目錄
1.簡介 2. Java代理基礎 3. Java代理和規范 4.編寫您的第一個Java代理 5.運行Java代理 6.接下來 7.下載源代碼1.簡介
在本教程的最后一部分中,我們將討論Java代理,這對于在那里的常規Java開發人員是一個真正的魔咒。 通過執行字節碼的直接修改,Java代理能夠“侵入”運行時在JVM上運行的Java應用程序的執行。 Java代理的功能和危險一樣強大:它們幾乎可以執行所有操作,但是如果出現問題,它們很容易使JVM崩潰。
這部分的目的是通過解釋Java代理如何工作,如何運行它們以及展示一些簡單的示例來揭開Java代理的神秘面紗,Java代理顯然具有優勢。
2. Java代理基礎
本質上,Java代理是遵循一組嚴格約定的常規Java類。 代理類必須實現一個public static void premain(String agentArgs, Instrumentation inst)方法,該方法成為代理的入口點(類似于常規Java應用程序的main方法)。
初始化Java虛擬機(JVM)后,將按照在JVM啟動時指定代理的順序調用每個代理的每個此類premain(String agentArgs, Instrumentation inst)方法。 完成此初始化步驟后,將調用真實的Java應用程序main方法。
但是,如果該類未實現public static void premain(String agentArgs, Instrumentation inst)方法,則JVM將嘗試查找并調用另一個重載版本的public static void premain(String agentArgs) 。 請注意,每個premain方法必須返回才能啟動階段。
最后但并非最不重要的一點是,Java代理類還可以具有在JVM啟動后啟動代理時使用的public static void agentmain(String agentArgs, Instrumentation inst)或public static void agentmain(String agentArgs)方法。
乍看之下看起來很簡單,但Java代理實現還應提供其他一些內容作為其包裝的一部分:清單。 清單文件通常位于META-INF文件夾中,名為MANIFEST.MF ,包含與包分發有關的各種元數據。
我們在本教程中并未討論清單,因為大多數時候它們都不是必需的,但是Java代理不是這種情況。 為打包為Java歸檔(或簡稱JAR)文件的Java代理定義了以下屬性:
| 清單屬性 | 描述 |
| 初級班 | 在JVM啟動時指定了代理時,此屬性定義Java代理類:包含premain方法的類。 在JVM啟動時指定代理時,此屬性是必需的。 如果該屬性不存在,JVM將中止。 |
| 代理級 | 如果實現支持在JVM啟動后的某個時間啟動Java代理的機制,則此屬性指定代理類:包含agentmain方法的類。 此屬性是必需的,如果不存在該代理,則不會啟動代理。 |
| 引導類路徑 | 引導類加載器要搜索的路徑列表。 路徑代表目錄或庫。 |
| 可以重新定義類 | true或false值,不區分大小寫,并且定義是否具有重新定義此代理所需的類的能力。 此屬性是可選的,默認值為false 。 |
| 可以重新轉換類 | true或false值,不區分大小寫,并且定義是否具有重新轉換此代理所需的類的能力。 此屬性是可選的,默認值為false 。 |
| 可以設置本機方法前綴 | true或false值,不區分大小寫,并且定義是否可以設置此代理所需的本機方法前綴。 此屬性是可選的,默認值為false 。 |
有關更多詳細信息,請隨時查閱專用于Java代理和工具的官方文檔 。
3. Java代理和規范
Java代理的檢測功能確實是無限的。 最引人注意的包括但不限于:
- 能夠在運行時重新定義類。 重新定義可能會更改方法主體,常量池和屬性。 重新定義不得添加,刪除或重命名字段或方法,更改方法的簽名或更改繼承。
- 能夠在運行時重新轉換類。 重新轉換可能會更改方法主體,常量池和屬性。 重新轉換不得添加,刪除或重命名字段或方法,更改方法的簽名或更改繼承。
- 通過允許重命名使用前綴來修改本機方法解析的失敗處理的能力。
請注意,在應用轉換或重新定義后,不會檢查,驗證和安裝重新轉換或重新定義的類字節碼。 如果生成的字節碼錯誤或不正確,則將引發異常,這可能會使JVM完全崩潰。
4.編寫您的第一個Java代理
在本節中,我們將通過實現我們自己的類轉換器來編寫一個簡單的Java代理。 話雖如此,使用Java代理的唯一缺點是,為了完成或多或少的有用轉換,需要直接字節碼操作技能。 而且,不幸的是,Java標準庫沒有提供任何API(至少是有文檔的API)來使這些字節碼操作成為可能。
為了填補這一空白,富有創造力的Java社區提出了一些優秀的,非常成熟的庫,例如Javassist和ASM ,僅舉幾例。 在這兩種方法中,Javassist使用起來更簡單,這就是為什么它成為我們將要用作字節碼操作解決方案的原因。 到目前為止,這是我們第一次無法在Java標準庫中找到合適的API,除了使用社區提供的API之外別無選擇。
我們將要處理的示例相當簡單,但它取自于實際的用例。 假設我們要捕獲Java應用程序打開的每個HTTP連接的URL。 有很多方法可以通過直接修改Java源代碼來做到這一點,但讓我們假設由于許可證策略或其他原因導致源代碼不可用。 打開HTTP連接的類的典型示例如下所示:
public class SampleClass {public static void main( String[] args ) throws IOException {fetch("http://www.google.com");fetch("http://www.yahoo.com");}private static void fetch(final String address) throws MalformedURLException, IOException {final URL url = new URL(address); final URLConnection connection = url.openConnection();try( final BufferedReader in = new BufferedReader(new InputStreamReader( connection.getInputStream() ) ) ) {String inputLine = null;final StringBuffer sb = new StringBuffer();while ( ( inputLine = in.readLine() ) != null) {sb.append(inputLine);} System.out.println("Content size: " + sb.length());}} }Java代理非常適合解決此類挑戰。 我們只需要定義一個轉換器,即可通過注入代碼以將輸出生成到控制臺來稍微修改sun.net.www.protocol.http.HttpURLConnection構造函數。 聽起來很嚇人,但是使用ClassFileTransformer和Javassist非常簡單。 讓我們看一下這樣的轉換器實現:
public class SimpleClassTransformer implements ClassFileTransformer {@Overridepublic byte[] transform( final ClassLoader loader, final String className,final Class<?> classBeingRedefined, final ProtectionDomain protectionDomain,final byte[] classfileBuffer ) throws IllegalClassFormatException {if (className.endsWith("sun/net/www/protocol/http/HttpURLConnection")) {try {final ClassPool classPool = ClassPool.getDefault();final CtClass clazz = classPool.get("sun.net.www.protocol.http.HttpURLConnection");for (final CtConstructor constructor: clazz.getConstructors()) {constructor.insertAfter("System.out.println(this.getURL());");}byte[] byteCode = clazz.toBytecode();clazz.detach();return byteCode;} catch (final NotFoundException | CannotCompileException | IOException ex) {ex.printStackTrace();}}return null;} }ClassPool和所有CtXxx類( CtClass , CtConstructor )都來自Javassist庫。 我們所做的轉換是非常幼稚的,但此處僅用于演示目的。 首先,由于我們僅對HTTP通信感興趣,因此sun.net.www.protocol.http.HttpURLConnection是來自標準Java庫的類。
請注意,而不是“。” 分隔符, className帶有“ /”。 其次,我們尋找HttpURLConnection類,并通過注入System.out.println(this.getURL());修改其所有構造函數System.out.println(this.getURL()); 最后的聲明。 最后,我們返回了該類轉換后的版本的新字節碼,因此它將由JVM使用,而不是原始版本。
這樣,Java代理的premain方法的作用就是將SimpleClassTransformer類的實例SimpleClassTransformer到檢測上下文中:
public class SimpleAgent {public static void premain(String agentArgs, Instrumentation inst) {final SimpleClassTransformer transformer = new SimpleClassTransformer();inst.addTransformer(transformer);} }而已。 看起來很容易,同時又有些令人恐懼。 為了完成Java代理,我們必須提供適當的MANIFEST.MF,以便JVM能夠選擇正確的類。 這是必需屬性的相應最小集合(有關更多詳細信息,請參閱Java Agent Basics部分):
Manifest-Version: 1.0 Premain-Class: com.javacodegeeks.advanced.agent.SimpleAgent這樣一來,首先的Java代理就準備好進行一場真正的戰斗。 在本教程的下一部分中,我們將介紹一種與Java應用程序一起運行Java代理的方法。
5.運行Java代理
從命令行運行時,可以使用具有以下語義的-javaagent參數將Java代理傳遞到JVM實例:
-javaagent:<path-to-jar>[=options]其中<path-to-jar>是查找Java代理JAR歸檔文件的路徑,而options包含可以通過agentArgs參數更準確地傳遞給Java代理的其他選項。 例如,從編寫您的第一個Java代理 (使用Java 7版本)部分運行我們的Java代理的命令行如下所示(假定代理JAR文件位于當前文件夾中):
java -javaagent:advanced-java-part-15-java7.agents-0.0.1-SNAPSHOT.jar與advanced-java-part-15-java7.agents-0.0.1-SNAPSHOT.jar Java代理一起運行SampleClass類時,該應用程序將在控制臺上打印所有URL( Google和Yahoo! )。嘗試使用HTTP協議進行訪問(其次是Google和Yahoo!搜索主頁的內容大小):
http://www.google.com Content size: 20349 http://www.yahoo.com Content size: 1387在未指定Java代理的情況下運行相同的SampleClass類將僅在控制臺上輸出內容大小,而不輸出URL(請注意,內容大小可能會有所不同):
Content size: 20349 Content size: 1387JVM使運行Java代理變得簡單。 但是,請注意,任何錯誤或不正確的字節碼生成都可能使JVM崩潰,并可能丟失此時您的應用程序可能保存的重要數據。
6.接下來
到最后,高級Java教程也結束了。 希望您發現它是有用,實用和有趣的。 有許多主題尚未涵蓋,但是非常歡迎您繼續深入探討Java語言,平臺,生態系統和不可思議的社區的奇妙世界。 祝好運!
7.下載源代碼
您可以在此處下載本課程的源代碼: advanced-java-part-15
翻譯自: https://www.javacodegeeks.com/2015/09/java-agents.html
java代理模式
總結
以上是生活随笔為你收集整理的java代理模式_Java代理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电脑设屏保密码怎么设置密码(电脑屏保密码
- 下一篇: java美元兑换,(Java实现) 美元