天天看點

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 {
  @Override
  public 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: 1387
           

JVM使運作Java代理變得簡單。 但是,請注意,任何錯誤或不正确的位元組碼生成都可能使JVM崩潰,并可能丢失此時您的應用程式可能儲存的重要資料。

6.接下來

到最後,進階Java教程也結束了。 希望您發現它是有用,實用和有趣的。 有許多主題尚未涵蓋,但是非常歡迎您繼續深入探讨Java語言,平台,生态系統和不可思議的社群的奇妙世界。 祝好運!

7.下載下傳源代碼

您可以在此處下載下傳本課程的源代碼: advanced-java-part-15

翻譯自: https://www.javacodegeeks.com/2015/09/java-agents.html

java代理模式