序章
知識を蓄えるために、最初にこの記事をお読みください: JAVA Instrument
この場合、このInstrument
メカニズムを使用して、単純なホット アップデート ケースを実装します。
全体的な手順は次のとおりです。
premain
メソッドを含むパッケージを作成しますjar
。この方法では、定期的にファイルを検出し、ホット アップデートを実行します。- パラメータは、コマンド ラインからビジネス クラスを開始するときに使用されます
-javaagent
。java -javaagent:jarpath[=选项] Main
インターネット上にはMaven
パッケージングjar
を使用するケースがたくさんありますが、ここでは純粋なコマンド ライン アプローチについて話します。
コード
構造は次のとおりです
これはコンパイルされた構造です。ロジックの実装から始めましょう。
1.premain
メソッドを使用してプロキシ クラスを作成する
まずpremain
メソッドを含むクラスを宣言します。クラス名を次のように設定しますAgent
。このクラスは最初にスレッドを開始し、このスレッドは
25 秒後にUser
クラスを置き換えます。
その中でも、ファイルをバイト単位で読み取って置換することはInstrument
関連知識です。
package com.wyw;
import java.io.File;
import java.io.FileInputStream;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
import java.util.concurrent.TimeUnit;
public class Agent {
public static void premain(String arg, Instrumentation instrumentation) {
System.out.println("premain starting ~");
Thread thread = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
Class<?> person = Class.forName("com.wyw.User");
File file = new File("C:\\Users\\QTZ\\IdeaProjects\\hotfix\\src\\com\\wyw\\User.class");
FileInputStream fileInputStream = new FileInputStream(file);
byte[] bytes = fileInputStream.readAllBytes();
instrumentation.redefineClasses(new ClassDefinition(person, bytes));
} catch (Exception e) {
e.printStackTrace();
}
});
thread.start();
}
}
2. ビジネスクラスを書く
メインの起動クラス:
名前付きクラスを宣言しwyw
、User
このクラスのプロパティを 50 秒以内に出力します。
package com.wyw;
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args) throws InterruptedException {
User user = new User("wyw");
int i = 0;
while (i < 50) {
TimeUnit.SECONDS.sleep(1);
System.out.println(i++ + ": " + user.getName());
}
}
}
User クラスは次のように定義されます。
package com.wyw;
public class User {
private static final String DEFAULT_PREFIX = "User: ";
private String name;
public User(String name) {
this.name = name;
}
public String getName() {
return DEFAULT_PREFIX + name;
}
}
パッケージの作成jar
、クラスのコンパイル
javac
まず、上記の実装を次のようにコンパイルする必要があります。
PS C:\Users\QTZ\IdeaProjects\hotfix\src> javac .\com\wyw\User.java .\com\wyw\Agent.java .\com\wyw\Main.java
実行後、いくつかのファイルが生成されることがわかりますclass
。
次に、このパッケージを作成するには、パッケージ化用のリストを宣言する必要があります。その中でパスをjar
指定する必要があります。このリストの名前は次のとおりです。Premain-Class
MANIFEST.MF
Manifest-Version: 1.0
Premain-Class: com.wyw.Agent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
次に、コマンドラインで次のようにします。
PS C:\Users\QTZ\IdeaProjects\hotfix\src> jar -c -f agent.jar -m .\com\wyw\MANIFEST.MF
実行後に が生成されますagent.jar
。
これら 2 つの基本的な手順が完了したら、クラスを開始します。
PS C:\Users\QTZ\IdeaProjects\hotfix\src> java -javaagent:agent.jar Main
コンソールは次のような出力を生成します。
PS C:\Users\QTZ\IdeaProjects\hotfix\src> java -javaagent:agent.jar com.wyw.Main
premain starting ~
0: User: wyw
1: User: wyw
2: User: wyw
3: User: wyw
4: User: wyw
このケースのコード ロジックでは、クラスは 25 秒後に置き換えられます。つまり、User
その期間中にクラスを変更してコンパイルする必要があります。
たとえば、次のUser
ように変更します。
package com.wyw;
public class User {
private static final String DEFAULT_PREFIX = "!!!User: ";
private String name;
public User(String name) {
this.name = name;
}
public String getName() {
return DEFAULT_PREFIX + name;
}
}
次に、それをコンパイルします。
PS C:\Users\QTZ\IdeaProjects\hotfix\src> javac .\com\wyw\User.java
コンソール出力は次のように表示されます。
17: User: wyw
18: User: wyw
19: User: wyw
20: User: wyw
21: User: wyw
22: User: wyw
23: User: wyw
24: !!!User: wyw
25: !!!User: wyw
26: !!!User: wyw
27: !!!User: wyw
28: !!!User: wyw
29: !!!User: wyw
つまり、置き換えは成功です。
ホット アップデートではメソッドを追加または削除できないことに注意してください。
オリジナルをUser
次の方法で置き換えます。
package com.wyw;
public class User {
private static final String DEFAULT_PREFIX = "User: ";
private String name;
public User(String name) {
this.name = name;
}
public String getName() {
return addMethod() + DEFAULT_PREFIX + name;
}
private String addMethod() {
return "Add: ";
}
}
クラスを再コンパイルした後、実行時に次のエラーが報告されます。
20: User: wyw
21: User: wyw
22: User: wyw
23: User: wyw
java.lang.UnsupportedOperationException: class redefinition failed: attempted to add a method
at java.instrument/sun.instrument.InstrumentationImpl.redefineClasses0(Native Method)
at java.instrument/sun.instrument.InstrumentationImpl.redefineClasses(InstrumentationImpl.java:193)
at com.wyw.Agent.lambda$premain$0(Agent.java:28)
at java.base/java.lang.Thread.run(Thread.java:834)
24: User: wyw
25: User: wyw
要約する
通常、ホット アップデートを通じてメソッドを追加または削除することはできませんが、メソッドの内部実装を変更することはできます。
このケースは、関連するコード ロジックを単純に示しており、ホット アップデートと置換には固定時間と固定クラスが使用されます。
特定のプロジェクトに投影すると、そのロジックに従って現地の状況に応じてロジックを実装したり、何らかの仕様を策定したりする必要があります。
ここでさらに私の考えをいくつか述べます。
いつチェックするか
ここでは定期的にチェックしていますが、エンジニアリングでは定期的にチェックするように修正する必要があります。
どのクラスを更新する必要があるかを判断する
ここで私は死に向かって直接書きます。他のアプローチには次のようなものがあります。
- ファイルを使用してホット アップデートが必要なパスを保存し、このファイルを読み取ります。
- 現在のクラスの変更時刻を保存し、時刻が変更されたときに更新します。