以下の内容はhttps://kazuhira-r.hatenablog.com/entry/2026/01/04/122052より取得しました。


Byte Buddyを使ってJava agentを書いてみる

これは、なにをしたくて書いたもの?

前にByte Buddyを使ったり、Java agentについて見てみたりしてみました。

Byte Buddyでバイトコードを生成・操作してみる - CLOVER🍀

Java agentを書いてみる - CLOVER🍀

今度はByte Buddyを使って、Java agentを書いてみたいと思います。

ヒント

Byte BuddyでJava agentを書くにあたり、なにを参考にすればいいのかというところですが、ドキュメントには
ほとんど書かれていません。

こちらの中にある「Creating Java agents」というセクションだけですね。

Why runtime code generation? / Creating a class

ドキュメントを見ると、AgentBuilderというクラスを使うようです。

AgentBuilder (Byte Buddy (without dependencies) 1.18.3 API)

Byte Buddyにはいくつかのアーティファクトがあり、以前JUnitのテストコード内でJava agentをインストール
した時にはbyte-buddy-agentというアーティファクトを使いました。

AgentBuilderbyte-buddyに含まれているので、こちらを使うだけであればbyte-buddy-agentは入りません。

ドキュメントで出てくるように、実行中のJava VMに対してJava agentを組み込む際に必要になるアーティファクト
ようです。

public static void main(String[] args) {
    premain("", ByteBuddyAgent.install());
    System.out.println(new Bar());
}

今回は-javaagentを指定する方法にします。

環境

今回の環境はこちら。

$ java --version
openjdk 25.0.1 2025-10-21
OpenJDK Runtime Environment (build 25.0.1+8-Ubuntu-124.04)
OpenJDK 64-Bit Server VM (build 25.0.1+8-Ubuntu-124.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.9.12 (848fbb4bf2d427b72bdb2471c22fced7ebd9a7a1)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 25.0.1, vendor: Ubuntu, runtime: /usr/lib/jvm/java-25-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "6.8.0-90-generic", arch: "amd64", family: "unix"

Java agentを組み込むアプリケーションを作成する

まずはJava agentを組み込むアプリケーションが必要ですね。

今回は小さく作ります。

    <properties>
        <maven.compiler.release>25</maven.compiler.release>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

こちらを変更対象のクラスにしましょう。

src/main/java/org/littlewings/MessageService.java

package org.littlewings;

public class MessageService {
    public String decorate(String message) {
        return "★★★ %s ★★★".formatted(message);
    }

    public String hello() {
        return "Hello";
    }
}

mainクラス。

src/main/java/org/littlewings/App.java

package org.littlewings;

public class App {
    public static void main(String... args) {
        MessageService messageService = new MessageService();

        String word = args.length > 0 ? args[0] : "word";

        System.out.printf("decorate = %s%n", messageService.decorate(word));
        System.out.printf("hello = %s%n", messageService.hello());
    }
}

パッケージングして実行。

$ mvn clean package


$ java -cp target/sample-app-0.0.1-SNAPSHOT.jar org.littlewings.App
decorate = ★★★ word ★★★
hello = Hello

これで準備はできました。

Byte Buddyを使ってJava agentを作成する

では、Byte Buddyを使ってJava agentを作ります。

Maven依存関係など。

    <properties>
        <maven.compiler.release>25</maven.compiler.release>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>net.bytebuddy</groupId>
            <artifactId>byte-buddy</artifactId>
            <version>1.18.3</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.6.1</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <transformers>
                        <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                            <manifestEntries>
                                <Premain-Class>org.littlewings.bytebuddy.MyAgent</Premain-Class>
                                <Can-Redefine-Classes>true</Can-Redefine-Classes>
                                <Can-Retransform-Classes>true</Can-Retransform-Classes>
                            </manifestEntries>
                        </transformer>
                    </transformers>
                </configuration>
            </plugin>
        </plugins>
    </build>

作成したクラスはこちら。

src/main/java/org/littlewings/bytebuddy/MyAgent.java

package org.littlewings.bytebuddy;

import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.FixedValue;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.utility.JavaModule;

public class MyAgent {
    public static void premain(String agentArgs, Instrumentation inst) {
        new AgentBuilder.Default()
                .type(ElementMatchers.named("org.littlewings.MessageService"))
                .transform(new AgentBuilder.Transformer() {
                    @Override
                    public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, ProtectionDomain protectionDomain) {
                        return builder.method(ElementMatchers.named("hello")).intercept(FixedValue.value("HELLO?"));
                    }
                })
                .transform((builder, typeDescription, classLoader, module, protectionDomain) ->
                        builder.method(ElementMatchers.named("decorate"))
                                .intercept(MethodDelegation.withDefaultConfiguration().filter(ElementMatchers.named("decorate")).to(MyAgent.class))
                )
                .installOn(inst);
    }

    public static String decorate(String message) {
        return "※※※ %s ※※※".formatted(message);
    }
}

AgentBuilder.Defaultというのは、セルフインジェクションとrebaseを有効にして使える戦略のようです。
ドキュメントどおりなのですが、まずはこれを選ぶのがよいのでしょう。

By default, Byte Buddy ignores any types loaded by the bootstrap class loader and any synthetic type. Self-injection and rebasing is enabled.

        new AgentBuilder.Default()

AgentBuilder.Default (Byte Buddy (without dependencies) 1.18.3 API)

typeで対象を絞り込み

                .type(ElementMatchers.named("org.littlewings.MessageService"))

transformで変換内容を設定します。

                .transform(new AgentBuilder.Transformer() {
                    @Override
                    public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, ProtectionDomain protectionDomain) {
                        return builder.method(ElementMatchers.named("hello")).intercept(FixedValue.value("HELLO?"));
                    }
                })

初見だと引数の意味がわからない気もしますが、DynamicType.BuilderはByte Buddyで最初の方で使うクラスです。
DynamicType.Builder#makeを呼び出すと、DynamicType.Unloadedが返ってきます。

        DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
                .subclass(Object.class)
                .method(ElementMatchers.named("toString"))
                .intercept(FixedValue.value("Hello World!"))
                .make();

DynamicType.Builder (Byte Buddy (without dependencies) 1.18.3 API)

つまり、この中は適用対象の型を絞り込んだ後のルールをByte Buddyの使い方で書けばよいのです。
ここではhelloメソッドの内容を固定値に書き換えています。

                        return builder.method(ElementMatchers.named("hello")).intercept(FixedValue.value("HELLO?"));

慣れたらLambda式で書いてもよいでしょう。ここでは今回定義したクラスの中にある別のメソッドに転送しています。

                .transform((builder, typeDescription, classLoader, module, protectionDomain) ->
                        builder.method(ElementMatchers.named("decorate"))
                                .intercept(MethodDelegation.withDefaultConfiguration().filter(ElementMatchers.named("decorate")).to(MyAgent.class))
                )

最後にInstrumentationを使っておしまい。

                .installOn(inst);

パッケージング。

$ mvn clean package

使ってみます。

$ java -cp target/sample-app-0.0.1-SNAPSHOT.jar -javaagent:/path/to/target/sample-agent-0.0.1-SNAPSHOT.jar org.littlewings.App
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
WARNING: sun.misc.Unsafe::objectFieldOffset has been called by net.bytebuddy.dynamic.loading.ClassInjector$UsingUnsafe$Dispatcher$CreationAction (file:/home/kazuhira/study/java/clover/byte-buddy-examples/byte-buddy-agent/sample-agent/target/sample-agent-0.0.1-SNAPSHOT.jar)
WARNING: Please consider reporting this to the maintainers of class net.bytebuddy.dynamic.loading.ClassInjector$UsingUnsafe$Dispatcher$CreationAction
WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release
decorate = ※※※ word ※※※
hello = HELLO?

動きました!

decorate = ※※※ word ※※※
hello = HELLO?

ですが、警告もたくさん出ていますね。これはByte BuddyがUnsafeを使っているからのようです。Java 24以降だと
警告されるようです。

これについてはIssueがありました。

sun.misc.Unsafe deprecations in Java 24 · Issue #1803 · raphw/byte-buddy · GitHub

-Dnet.bytebuddy.safe=trueというシステムプロパティーを使うと動作を変更できるようです。

$ java -cp target/sample-app-0.0.1-SNAPSHOT.jar -javaagent:/path/to/target/sample-agent-0.0.1-SNAPSHOT.jar -Dnet.bytebuddy.safe=true org.littlewings.App
decorate = ※※※ word ※※※
hello = HELLO?

たぶん速度的にはUnsafeを使った方が速いと思うのですが、今回はノイズになるので-Dnet.bytebuddy.safe=true
付けておくことにします。

組み込み対象のライブラリーをJava agentの依存関係に入れられる?

組み込み対象のアプリケーション(というかライブラリー)をJava agent側に含めても呼び出せるのかどうか、
というのが気になりました。

試してみましょう。

組み込み対象のアプリケーションに、こういうクラスを追加。

src/main/java/org/littlewings/Foo.java

package org.littlewings;

public class Foo {
    public static String delegate(String message) {
        return "!!! %s !!!".formatted(message);
    }
}

インストール。

$ mvn clean install

Java agent側には依存関係を追加します。

    <properties>
        <maven.compiler.release>25</maven.compiler.release>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>net.bytebuddy</groupId>
            <artifactId>byte-buddy</artifactId>
            <version>1.18.3</version>
        </dependency>

        <dependency>
            <groupId>org.littlewings</groupId>
            <artifactId>sample-app</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.6.1</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <transformers>
                        <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                            <manifestEntries>
                                <Premain-Class>org.littlewings.bytebuddy.MyAgent</Premain-Class>
                                <Can-Redefine-Classes>true</Can-Redefine-Classes>
                                <Can-Retransform-Classes>true</Can-Retransform-Classes>
                            </manifestEntries>
                        </transformer>
                    </transformers>
                </configuration>
            </plugin>
        </plugins>
    </build>

Java agentで起動するクラス。

src/main/java/org/littlewings/bytebuddy/MyAgent.java

package org.littlewings.bytebuddy;

import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.FixedValue;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.utility.JavaModule;
import org.littlewings.Foo;

public class MyAgent {
    public static void premain(String agentArgs, Instrumentation inst) {
        new AgentBuilder.Default()
                .type(ElementMatchers.named("org.littlewings.MessageService"))
                .transform(new AgentBuilder.Transformer() {
                    @Override
                    public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, ProtectionDomain protectionDomain) {
                        return builder.method(ElementMatchers.named("hello")).intercept(FixedValue.value("HELLO?"));
                    }
                })
                .transform((builder, typeDescription, classLoader, module, protectionDomain) ->
                        builder.method(ElementMatchers.named("decorate"))
                                .intercept(MethodDelegation.withDefaultConfiguration().filter(ElementMatchers.named("delegate")).to(Foo.class))
                )
                .installOn(inst);
    }
}

ここで転送先に、依存ライブラリーに追加したクラスに設定。

                .transform((builder, typeDescription, classLoader, module, protectionDomain) ->
                        builder.method(ElementMatchers.named("decorate"))
                                .intercept(MethodDelegation.withDefaultConfiguration().filter(ElementMatchers.named("delegate")).to(Foo.class))
                )

パッケージングして

$ mvn clean package

実行。

$ java -cp target/sample-app-0.0.1-SNAPSHOT.jar -javaagent:/path/to/target/sample-agent-0.0.1-SNAPSHOT.jar -Dnet.bytebuddy.safe=true org.littlewings.App
decorate = !!! word !!!
hello = HELLO?

呼び出せるものですね。

適用対象のクラスに下手に使ったりするとStackOverflowErrorになるなどもしましたが…使い方を間違えなければ
参照自体はできそうです。

あまりやらない方がいい気がしますが、押さえておきましょう。

おわりに

Byte Buddyを使ってJava agentを書いてみました。

けっこう構えていたのですが、これまでと同じようにDynamicType.Builderが使えることに気づくとそれほど
警戒するものでもないかもしれません。

これで実行時にクラスの変更ができるようになりました。




以上の内容はhttps://kazuhira-r.hatenablog.com/entry/2026/01/04/122052より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14