Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

高度な例

bytekinの高度なユースケースとパターン。

例1: カスタムClassLoader

変換を適用するカスタムClassLoaderを実装:

public class TransformingClassLoader extends ClassLoader {
    private final BytekinTransformer transformer;
    private final ClassLoader parent;

    public TransformingClassLoader(BytekinTransformer transformer, ClassLoader parent) {
        super(parent);
        this.transformer = transformer;
        this.parent = parent;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] classBytes = loadBytesFromClasspath(name);
            byte[] transformed = transformer.transform(name, classBytes);
            return defineClass(name, transformed, 0, transformed.length);
        } catch (IOException e) {
            throw new ClassNotFoundException("Cannot find " + name, e);
        }
    }

    private byte[] loadBytesFromClasspath(String className) throws IOException {
        String path = className.replace('.', '/') + ".class";
        try (InputStream is = parent.getResourceAsStream(path)) {
            return is.readAllBytes();
        }
    }
}

// 使用方法
BytekinTransformer transformer = new BytekinTransformer.Builder(MyHooks.class).build();
ClassLoader loader = new TransformingClassLoader(transformer, ClassLoader.getSystemClassLoader());
Class<?> clazz = loader.loadClass("com.example.MyClass");

例2: Javaエージェント

バイトコード変換のためのJavaエージェントを作成:

public class BytekinAgent {
    public static void premain(String agentArgs, Instrumentation inst) {
        BytekinTransformer transformer = new BytekinTransformer.Builder(MyHooks.class).build();
        inst.addTransformer((loader, className, klass, pd, bytecode) -> {
            return transformer.transform(className, bytecode);
        });
    }
}

起動: java -javaagent:bytekin-agent.jar MyApplication

例3: アスペクト指向プログラミング(AOP)

横断的関心事を実装:

@ModifyClass("com.example.UserService")
public class AuditingAspect {

    @Inject(methodName = "save", methodDesc = "(Lcom/example/User;)V", at = At.HEAD)
    public static CallbackInfo auditBefore(Object user) {
        System.out.println("Audit: save() started");
        return CallbackInfo.empty();
    }

    @Inject(methodName = "delete", methodDesc = "(I)V", at = At.HEAD)
    public static CallbackInfo auditDelete(int id) {
        System.out.println("Audit: delete(" + id + ") started");
        return CallbackInfo.empty();
    }
}

例4: 遅延初期化

遅延ローディングパターンを実装:

@ModifyClass("com.example.Repository")
public class LazyLoadingHooks {
    private static Object resource;

    @Inject(methodName = "initialize", methodDesc = "()V", at = At.HEAD)
    public static CallbackInfo lazyInit(CallbackInfo ci) {
        if (resource == null) {
            synchronized (LazyLoadingHooks.class) {
                if (resource == null) {
                    resource = loadExpensiveResource();
                }
            }
        }
        ci.cancelled = true;
        return ci;
    }

    private static Object loadExpensiveResource() {
        // 高コストな初期化
        return new Object();
    }
}

例5: 動的設定

設定に基づいて動作を変更:

@ModifyClass("com.example.Service")
public class DynamicConfigHooks {
    private static final Properties config = new Properties();

    static {
        try {
            config.load(new FileInputStream("config.properties"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Inject(methodName = "process", methodDesc = "(Ljava/lang/String;)V", at = At.HEAD)
    public static CallbackInfo checkConfig(String input, CallbackInfo ci) {
        boolean enabled = Boolean.parseBoolean(config.getProperty("feature.enabled", "false"));
        if (!enabled) {
            ci.cancelled = true;
        }
        return ci;
    }
}

例6: 多層変換

複数のトランスフォーマーを順次適用:

public class MultiLayerTransformation {
    public static void main(String[] args) {
        BytekinTransformer logging = new BytekinTransformer.Builder(LoggingHooks.class).build();
        BytekinTransformer security = new BytekinTransformer.Builder(SecurityHooks.class).build();
        BytekinTransformer caching = new BytekinTransformer.Builder(CachingHooks.class).build();

        byte[] original = getClassBytecode("com.example.Service");

        // 層ごとに適用
        byte[] withLogging = logging.transform("com.example.Service", original);
        byte[] withSecurity = security.transform("com.example.Service", withLogging);
        byte[] withCaching = caching.transform("com.example.Service", withSecurity);

        Class<?> transformed = loadClass(withCaching);
    }
}

例7: パフォーマンスプロファイリング

ソース変更なしでプロファイリングを追加:

@ModifyClass("com.example.CriticalPath")
public class ProfilingHooks {
    private static final ThreadLocal<Long> timer = ThreadLocal.withInitial(() -> 0L);

    @Inject(methodName = "compute", methodDesc = "()Ljava/lang/Object;", at = At.HEAD)
    public static CallbackInfo startProfiling() {
        timer.set(System.nanoTime());
        return CallbackInfo.empty();
    }

    @Inject(methodName = "compute", methodDesc = "()Ljava/lang/Object;", at = At.RETURN)
    public static CallbackInfo endProfiling() {
        long duration = System.nanoTime() - timer.get();
        System.out.println("Duration: " + (duration / 1_000_000.0) + "ms");
        return CallbackInfo.empty();
    }
}

例8: レジリエンスパターン

リトライロジックを追加:

@ModifyClass("com.example.HttpClient")
public class ResilienceHooks {
    private static final int MAX_RETRIES = 3;

    @Inject(methodName = "request", methodDesc = "(Ljava/lang/String;)Ljava/lang/String;",
            at = At.HEAD)
    public static CallbackInfo addRetry(String url, CallbackInfo ci) {
        String result = null;
        int attempt = 0;

        while (attempt < MAX_RETRIES) {
            try {
                result = executeRequest(url);
                break;
            } catch (Exception e) {
                attempt++;
                if (attempt >= MAX_RETRIES) throw e;
            }
        }

        ci.cancelled = true;
        ci.returnValue = result;
        return ci;
    }

    private static String executeRequest(String url) throws Exception {
        // HTTPリクエストを実行
        return "";
    }
}

例9: 可観測性

メトリクスを収集:

@ModifyClass("com.example.DataStore")
public class ObservabilityHooks {
    private static final AtomicLong callCount = new AtomicLong(0);
    private static final AtomicLong errorCount = new AtomicLong(0);

    @Inject(methodName = "query", methodDesc = "(Ljava/lang/String;)Ljava/util/List;",
            at = At.HEAD)
    public static CallbackInfo trackCall(String query) {
        callCount.incrementAndGet();
        return CallbackInfo.empty();
    }

    @Invoke(
        targetMethodName = "query",
        targetMethodDesc = "(Ljava/lang/String;)Ljava/util/List;",
        invokeMethodName = "throwException",
        invokeMethodDesc = "()V",
        shift = Shift.BEFORE
    )
    public static CallbackInfo trackError() {
        errorCount.incrementAndGet();
        return CallbackInfo.empty();
    }

    public static void printMetrics() {
        System.out.println("Calls: " + callCount.get());
        System.out.println("Errors: " + errorCount.get());
    }
}

例10: 移行戦略

古いAPIから新しいAPIへ段階的に移行:

@ModifyClass("com.example.Application")
public class MigrationHooks {
    private static final boolean USE_NEW_API = true;

    @Redirect(
        targetMethodName = "main",
        targetMethodDesc = "([Ljava/lang/String;)V",
        redirectMethodName = "oldSearch",
        redirectMethodDesc = "(Ljava/lang/String;)Ljava/util/List;",
        from = "search",
        to = USE_NEW_API ? "newSearch" : "oldSearch"
    )
    public static void migrateAPI() {
        // 徐々に新しい実装にルーティング
    }
}

次のステップ