広く使われているJavaバイトコードエンジニアリングライブラリであるByteBuddyの新しいリリースではJava 11を完全にサポートし、Java 8以降に導入されたクラスファイルとバイトコードの新機能をすべてサポートした。そこには新しいConstantDynamic (condyと呼ばれることもある) とJava 11のNestmatesもある。
InfoQは詳しく知るためにByteBuddyの作者であるRafael Winterhalter氏と話した。
InfoQ「話す時間を取ってくれてありがとうございます。まずByte Buddyとは何かを説明してもらえませんでしょうか?どのような機能を持っているのでしょうか?またどのような種類のアプリケーションに対して使うものなのでしょうか?このプロジェクトはどのくらい運営しているのでしょう?開発者はByte Buddyをどのように使い始めればよいでしょうか?」
Rafael Winterhalter氏「Byte Buddyはコード生成のライブラリで、シンプルなJavaのAPIを使って新しいクラスを定義したり既存のクラスを変更したりできます。こうしたことを実現するため、ライブラリはJavaのバイトコードを生成したり操作したりします。バイトコードレベルで動作するため、どのようなJVM言語で書かれたコードであってもやり取りでき、ライブラリはJavaアプリケーションの実行時に使用され、その時点で実行しているコードを変更します。そこには自身のコードさえ含まれます。
Byte Buddyは主に他のライブラリやフレームワークで使われます。たとえばHibernateはByte Buddyを使ってエンティティのプロキシを実装していますし、Mockitoはモッククラスを生成するために使っています。しかし次第にByte BuddyはJavaエージェントの開発にも使われてきています。そのようなJavaエージェントはアプリケーション全体の振る舞いを変更します。エージェントを使って、たとえばInstanaといったAPMツールはByte Buddyを使って実行時にアプリケーションのメトリクスを収集します。
私は2014年にByte Buddyの開発に取り組み始め、2015年にベータでない最初のバージョンをリリースしました。その後非常に多くの出来事がこれを前に推し進めてくれました。今日現在ライブラリのダウンロード数は年間100万近いです。
始めるのに必要なのは、ライブラリをプロジェクトに追加して、DSLを使ってクラスを生成するだけです。GitHubページとByte Buddyのホームページともに単純なクラスを生成する方法の簡単なサンプルを載せています。このサンプルを拡張してwebページには包括的なドキュメントがあり、さまざまなブログやYouTube、Vimeoに多くの資料があります。
InfoQ「新しいリリースについて話しましょう。最近追加されたものは何ですか?とくにユーザが長く望んでいた新しい機能はありますか?」
Winterhalter氏「最新のリリースでJava 11と12をサポートできるように開発してきました。しかしJavaのモジュールシステムのサポートを追加することに時間のほとんどを費やしました。とても長い道のりでした。しかし最新のリリースでモジュールシステムのサポートは安定したものとなり、来る1.9.0のリリースでByte Buddyにはmodule-info.classさえも追加されます。同時にライブラリはこのクラスファイルを無視するバージョンであるJava 6や7、8とも互換性を保ちます。
機能リクエストには一般的に新しいJavaバージョンのサポートがあります。サポートしていないと新しいVM上でのビルドでプロジェクトを止めてしまうことがあるからです。同様に、多くの機能リクエストにはKotlinやScalaといったJVM言語のサポートが必然的に伴います。これらにはバイトコード変換となるといくつか特異点があります。Java言語に新しい機能を追加する場合、とても保守的に実行し、筋が通っていればJVMの能力を拡大することもあります。他の言語は特定の振る舞いをエミュレートしようとすることがあり、これによってByte Buddyがタスクを失敗してしまうことがあります。」
InfoQ「新しいリリースサイクルに移りましょう。ツール作成者として、ペースが早くなったこととクラスファイルフォーマットの変更からどんな影響を受けていますか?」
Winterhalter氏「Javaの全バージョンに対して、Byte BuddyはJavaのクラスファイルフォーマットの変更を適応させる必要があります。こうした変更はたいていささいなものですが、かなり複雑でもあります。たとえばJava 8がリリースされたとき、Byte Buddyはデフォルトメソッドによる、インタフェース中のコードというコンセプトに適応しなければなりませんでした。これは小さな変更のように見えるかもしれませんが、メソッドはクラスの階層内に定義されているという仮定が当たり前のように実装内にいくつもあったため、多くのリファクタリングが必要でした。Javaのリリースがより早いペースとなり、残念ながら私の仕事は少しより退屈なものとなりますが、結局のところ私のプロフェッショナルとしての人生の中心にあるプラットフォームに追加される新しい機能について不満を述べることなどできません。
今まで積み重ねてきた中で私が直面している1つの問題は多くのライブラリがByte Buddyに依存しているということです。そのためJavaの新しいメジャーリリース後、より早くサポートすることが要求されます。この更新がないと、Byte Buddyに依存しているライブラリが新しいJavaのバージョンをサポートできません。これでは他のメンテナが適応しようとしているのを阻害してしまいます。同時に、Mavenを始め多くのツールがJavaの新しいバージョンをサポートしていないことがあります。そうなると私の作業は困難になります。そして半年ごとというのは早すぎます。しかし他のツールベンダはすでにこの変化に適応しているように思います。Java 10からJava 12のサポートは前回のリリースに比べてかなり簡単でした。それは1つのアップデートに含まれる変更が少ないからでもあります。」
InfoQ「Java 11は新機能としてConstantDynamicを出しました。何が重要なのでしょうか?これはどのように動作するのでしょうか?InvokeDynamicに関連するものなのでしょうか?もしそうなら、どのように関連するのでしょうか?」
Winterhalter氏「Java開発者は一般的に定数をstatic finalフィールドの値に格納して定義します。しかしJavaのクラスファイルフォーマットではクラスファイルの定数がフィールドに格納されていなくても他のシンボルから参照されてよいのです。いくつかの型はすでにリテラルとしてそのようなシンボルを表現しています。たとえばリテラルとして表現されるJavaの文字列はクラスファイルのコンスタントプールにあるそのようなシンボルと結びついています。」
ConstantDynamicによってコンスタントプール内で参照される定数値としてあらゆる定数を表現できるようになります。クラスファイル定数を使うと、定数の値をクラスのロード時ではなく初回の利用時に生成すればよいという大きなアドバンテージがあります。その定数がstatic finalフィールドであってもです。ConstantDynamicを使い、将来JVMで不必要に早いローディングを多く避けられるでしょう。たとえば、JVMは起動時にJVMがサポートするあらゆる言語を参照するLocaleクラスを初期化しなければなりません。この初期化はかなりコストがかかりますが不必要なときもあります。ほとんどのプログラムはデフォルトの言語しか使わないからです。ConstantDynamicを使うことで、コアライブラリは将来JVMの起動がより早くなることで改善され、もちろん他のライブラリも同様に改善されるでしょう。
今日現在ではConstantDynamicはJava言語もしくは他のJVM言語から利用されていません。しかしJVMでのサポートは完全に動作しているので、Byte Buddyにはそのような動的な定数をバイトコードで生成する機能があります。動的な定数は結果として定数の値を返すブートストラップメソッドを実装することで作成されます。その後このブートストラップメソッドは定数の値が利用される場所で参照されます。
ConstantDynamicとInvokeDynamicの両方について詳細と、Byte Buddyでその2つを利用する方法を私はブログに書きました。InvokeDynamicと実際かなり似ています。違いはInvokeDynamicがブートストラップメソッドを定数の値ではなくコールサイトをバインドするために使うというところです。もちろんByte Buddyは同様にAPIを提供しています。」
InfoQ氏「他にJava 8以降でクラスファイルのフォーマットに対する重要な変更はありますか?ByteBuddyはどのようにその変更に対応しているのでしょうか?」
Winterhalter氏「ConstantDynamicに加えてJVMはネストメイトという概念を追加しました。ネストしたクラスでのメソッドのアクセス制御をよりよくするためです。それぞれのクラスファイルでネストメイトとしてクラスを2つ定義することで、それらは互いのプライベートメソッドを呼び出せる特権を得ます。以前は、ネストしたクラスはjavacコンパイラがそうした呼び出しのためにパッケージプライベートなアクセサメソッドを追加することで互いのプライベートメソッドを呼び出せていました。もちろんこれは意図したスコープを越えてクラスを公開してしまいます。はっきりとしたメカニズムの方が望ましく、また理解しやすいです。
Byte Buddyは既存のネストメイトをサポートしますが、現在DSLではネストメイトの変更、追加は許可していません。これは大きな機能でしょうし、今年のどこかで取り組みたいと考えています。」
InfoQ「将来はどうでしょうか?現在ロードマップにあり今後追加される機能に関してあなたの見解を教えてください。」
Winterhalter氏「今のところ、今後追加されるJavaの機能はほとんどすべて楽しみです。もっとも期待している機能はプロジェクトLoomです。これはJVMにネイティブな継続のサポートを追加するものです。ソフトウェアコンサルタントとしての私の仕事では、定期的にアクターモデルやリアクティブなコールバックといった抽象化を使って並列化を試みるプロジェクトに出くわします。こうしたアプリケーションは時間とともに複雑になっていくことがあります。ビジネスロジックが並列化をモデル化したお作法的なコードに埋もれてしまうからです。これはビジネスコードのリファクタリングを困難にしてしまうことがあります。ドメインロジックではなく従っている技術が要求するやり方で区分けされてしまうからです。Loomがこれを不要にし、多くのケースでビジネスコードの初めから終わりまで明確に並列化をモデル化してくれることを願っています。
Loomに加えて、Graalコンパイラへのサポートを拡大するのがとても楽しみです。JVMが世界でもっとも優れたランタイムの1つとしてその場所に今後も居続けられるよう、GraalコンパイラがJVMにとってもっとも重要な拠り所になると考えています。JITコンパイルに加えて、AOTコンパイルとネイティブイメージ作成機能は将来Javaが利用される領域を拡大するでしょう。またプロジェクトMetropolisがほとんどのJava開発者の知識とJVMの機能の間にある断絶のいくつかに橋をかける手助けとなると思います。」
InfoQ「他、読者と共有したいコメントや見解はありますか?」
Winterhalter氏「モジュールシステムのような機能や新しいリリースサイクルが利点よりもJava開発者に作業を強いるものであるとして、Java開発者の間にオラクルの管理に関して懐疑的な雰囲気を感じることがあります。
対照的に、オラクルがプラットフォームの長期的未来を脅かす多くのプログラムを最終的に保護していると私は思います。JVMはかつてないほどよい状態であり、今後やってくる多くの変更はこの基盤をベースにしています。Java開発者になりたいならわくわくするタイミングであり、私たち全員が楽しみにしています。」
興味を持った開発者には動画がいくつかあり、詳細も提供されている。
Rate this Article
- Editor Review
- Chief Editor Action