インジェクション変換
@Inject
アノテーションを使用すると、ターゲットメソッドの特定のポイントにカスタムコードを挿入できます。
基本的な使用法
@ModifyClass("com.example.Calculator")
public class CalculatorHooks {
@Inject(
methodName = "add",
methodDesc = "(II)I",
at = At.HEAD
)
public static CallbackInfo onAddStart(int a, int b) {
System.out.println("Adding: " + a + " + " + b);
return CallbackInfo.empty();
}
}
アノテーションパラメータ
methodName (必須)
インジェクション対象のメソッド名。
methodName = "add"
methodDesc (必須)
ターゲットメソッドシグネチャのJVMディスクリプタ。
methodDesc = "(II)I" // int add(int a, int b)
詳細はメソッドディスクリプタをご覧ください。
at (必須)
メソッド内のどこにコードをインジェクションするか。
At列挙型 - インジェクションポイント
At.HEAD
メソッドの最初、既存のコードの前にインジェクションします。
@Inject(methodName = "calculate", methodDesc = "()I", at = At.HEAD)
public static CallbackInfo atMethodStart() {
System.out.println("Method started");
return CallbackInfo.empty();
}
結果:
public int calculate() {
System.out.println("Method started"); // インジェクション済み
// 元のコードはここ
}
At.RETURN
メソッド内のすべてのreturn文の前にインジェクションします。
@Inject(methodName = "getValue", methodDesc = "()I", at = At.RETURN)
public static CallbackInfo beforeReturn(CallbackInfo ci) {
System.out.println("Returning: " + ci.returnValue);
return CallbackInfo.empty();
}
結果:
public int getValue() {
if (condition) {
System.out.println("Returning: " + value); // インジェクション済み
return value;
}
System.out.println("Returning: " + defaultValue); // インジェクション済み
return defaultValue;
}
At.TAIL
メソッドの最後、すべてのコードの後、暗黙的なreturnの前にインジェクションします。
@Inject(methodName = "cleanup", methodDesc = "()V", at = At.TAIL)
public static CallbackInfo atMethodEnd() {
System.out.println("Cleanup complete");
return CallbackInfo.empty();
}
フックメソッドのパラメータ
フックメソッドは、ターゲットメソッドと同じパラメータに加えてCallbackInfo
を受け取ります:
// ターゲットメソッド:
public String process(String input, int count) { ... }
// フックメソッド:
@Inject(methodName = "process", methodDesc = "(Ljava/lang/String;I)Ljava/lang/String;", at = At.HEAD)
public static CallbackInfo processHook(String input, int count, CallbackInfo ci) {
// パラメータにアクセス可能
return CallbackInfo.empty();
}
CallbackInfo - 動作の制御
CallbackInfo
オブジェクトを使用して、インジェクションの動作を制御できます:
public class CallbackInfo {
public boolean cancelled; // 元のコードをスキップするか?
public Object returnValue; // カスタム値を返すか?
}
実行のキャンセル
元のメソッドをスキップして早期にreturnします:
@Inject(methodName = "authenticate", methodDesc = "(Ljava/lang/String;)Z", at = At.HEAD)
public static CallbackInfo checkPermission(String user, CallbackInfo ci) {
if (!user.equals("admin")) {
ci.cancelled = true;
ci.returnValue = false; // 元のコードを実行せずfalseを返す
}
return ci;
}
カスタム戻り値
元の結果の代わりにカスタム値を返します:
@Inject(methodName = "getCached", methodDesc = "()Ljava/lang/Object;", at = At.HEAD)
public static CallbackInfo useCachedValue(CallbackInfo ci) {
Object cached = getFromCache();
if (cached != null) {
ci.cancelled = true;
ci.returnValue = cached;
}
return ci;
}
複数のインジェクション
同じメソッドに複数回インジェクションできます:
@ModifyClass("com.example.Service")
public class ServiceHooks {
@Inject(methodName = "handle", methodDesc = "(Ljava/lang/String;)V", at = At.HEAD)
public static CallbackInfo logStart(String input) {
System.out.println("Start: " + input);
return CallbackInfo.empty();
}
@Inject(methodName = "handle", methodDesc = "(Ljava/lang/String;)V", at = At.RETURN)
public static CallbackInfo logEnd(String input) {
System.out.println("End: " + input);
return CallbackInfo.empty();
}
}
両方のインジェクションが適用されます。
インスタンスメソッド vs 静的メソッド
インスタンスメソッドの場合、最初のパラメータは通常this
(オブジェクトインスタンス)です:
// ターゲットインスタンスメソッド:
public class Calculator {
public int add(int a, int b) { return a + b; }
}
// フックは'this'を受け取れます:
@Inject(methodName = "add", methodDesc = "(II)I", at = At.HEAD)
public static CallbackInfo onAdd(Calculator self, int a, int b) {
System.out.println("Calculator instance: " + self);
return CallbackInfo.empty();
}
ベストプラクティス
- フックをシンプルに保つ: 複雑なロジックは別のメソッドに
- 例外を避ける: フックメソッド内で例外を処理する
- ガードにAt.HEADを使用: 早期に条件をチェック
- At.RETURNに注意: 複数のreturnには処理が必要
- 十分にテスト: インジェクションが正しく動作することを検証
例
完全な動作例については、例 - インジェクションをご覧ください。