SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

Javassistでクラスのバイトコードを直接書き換える

クラスのバイトコード操作によるアスペクト指向的開発

Javassistによるクラスの操作

 続いて、肝心のJavassistによるクラスの変換処理について見ていきましょう。まず、繰り返しによるノード処理に入る前に、ClassPoolクラスを使い、修正を行うクラスのCtClassインスタンスを用意しておきます。

pool = ClassPool.getDefault();
cc = pool.get(s);

 Javassistでは、クラスやフィールド、メソッドなどを扱うための独自クラスを用意しています。CtClassもその一つで、ClassPoolのgetにより、修正を行うクラスのCtClassを取得します。以後の具体的な操作は、このCtClassに対して行うことになります。

 さて、ノードの繰り返し処理部分に進みましょう。既に、XMLから必要な情報は用意できました。これらをもとにフィールドを作成します。フィールドの作成には、使用するタイプを示すCtClassを用意します。

CtClass ftypeclass = pool.get(fldtype);

 続いて、取得したタイプを示すCtClassとフィールド名、そして組み込むクラスであるCtClassを引数に指定して、フィールドを示すCtFieldを作成します。

CtField field = new CtField(ftypeclass, fldname, cc);
field.setModifiers(Modifier.PRIVATE);

 インスタンス作成後、setModifiersを呼び出してアクセス修飾子をprivateに変更しておきます。これでフィールドは用意できました。クラスのCtClassを調べ、既にフィールドがあるかどうかをチェックし、まだなければこのCtFieldを追加します。

try {
    CtField fx = cc.getDeclaredField(fldname);
} catch (NotFoundException e) {
    cc.addField(field);

 getDeclaredFieldは、定義されているフィールドのCtFieldを返すものです。フィールドが見つからなければNotFoundExceptionを投げます。これを利用し、見つからなかった場合には、まだフィールドがないとみなし、addFieldでフィールドを組み込みます。

 続いて、アクセサ・メソッドの追加です。これは、既に用意してあるメソッド用のString値がnullでなければ、addMethodを使ってメソッドをクラスに追加します。メソッドの追加は、あらかじめメソッドの内容をStringで用意しておきさえすれば、単にそれをメソッドとしてクラスに組み込むだけで終わります。

if (getter != null) {
    cc.addMethod(CtNewMethod.make(getter, cc));
}
if (setter != null) {
    cc.addMethod(CtNewMethod.make(setter, cc));
}

 以上でフィールドとアクセサ・メソッドの組み込みは終わりました。全繰り返しを終えたところで、最後にスーパークラスをCtClassのスーパークラスに設定し、ファイルを書き換えて作業終了です。

cc.setSuperclass(cc.getSuperclass());
cc.writeFile();

 CtFieldの作成と追加、addMethodによるメソッドの組み込み、これらが分かれば、Javassistによるクラスの改変は、思ったよりも簡単に行えることが分かりますね。

アプリケーションクラスの作成

 では、このBeanMakerを利用するアプリケーションクラスを作成しておきましょう。ごく簡単なものですが、BeanMakerAppクラスというのを用意してみました。

package jp.codezine;

public class BeanMakerApp {

    public static void main(String[] args) {
        if (args[1].equals("make")){
            BeanMaker.createClassFile(args[0]);
        }
        if (args[1].equals("add")){
            BeanMaker.createBeanClassByXml(args[0]);
        }
    }
}

 ここでは、実行時に渡される引数によってクラスファイルの作成とXMLによる改変をそれぞれ実行できるようにしてあります。第1引数には操作するクラス名を、第2引数には「make」または「add」のいずれかを指定し、makeならばBeanMaker.createClassFileを、addならばBeanMaker.createBeanClassByXmlをそれぞれ呼び出します。

 以上で、Beanクラス作成のためのプログラムはすべて完成しました。では、実際にXMLファイルを定義して試してみましょう。

MyBean.xmlの定義と実行

 では、サンプルとして「MyBean」クラスを作成するための「MyBean.xml」ファイルを定義してみることにしましょう。このクラスでは、int値とString値のフィールドをそれぞれ1つずつ持たせることにします。

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE fields SYSTEM "beanmaker.dtd">
<fields>
 <title>My Sample Bean Class.</title>
 <field>
  <name>mytest1</name>
  <type>int</type>
  </field>
 <field>
  <name>mytest2</name>
  <type>java.lang.String</type>
  </field>
</fields>

 beanmaker.dtdを参照し定義を検証するので、<!DOCTYPE fields SYSTEM "beanmaker.dtd">という形で<!DOCTYPE>を設定しておきます。そして<fields>タグ内に<field>タグを使って各フィールドを定義します。

 作成したら、このXMLファイルを元にBeanクラスを作成し、フィールドを組み込んでみましょう。これらの操作は、それぞれプログラムの置かれているディレクトリから次のように実行するだけで行えます。

  1. クラスファイルの生成
  2. java jp.codezine.BeanMakerApp MyBean make
    
  3. XMLを使ったフィールドとアクセサの組み込み
  4. java jp.codezine.BeanMakerApp MyBean add
    

 beanmaker.dtdとMyBean.xmlは、それぞれプログラムが置かれているディレクトリに配置しておきます。1.でMyBean.classが作成され、2.でそれがさらに修正されるのが確認できたでしょうか。

図1-2 コマンドラインからjp.codezine.BeanMakerAppを実行する。引数にクラス名とmake/addのいずれかを指定する。
図1-2 コマンドラインからjp.codezine.BeanMakerAppを実行する。引数にクラス名とmake/addのいずれかを指定する。

生成されたBeanのソースコード

 では、改変されたクラスファイルは、本当にこちらが意図した通りに変更されているのでしょうか。念のため、デコンパイラを使ってクラスファイルをデコンパイルしてみました。使用したのは「Jad」です。

 まず、1.のクラスファイル生成を行った状態です。作成されたMyBean.classをデコンパイルすると、次のようなソースコードが得られました(コメント類は省略)。

public class MyBean {

    public MyBean() {
    }
}

 必要最小限のコードしか持たないクラスが生成されていることが分かります。続いて、2.のXMLを使ったフィールドとアクセサの組み込みを実行後、再度デコンパイルをしてみました。

public class MyBean {

    public MyBean() {
    }

    public int getMytest1() {
        return mytest1;
    }

    public void setMytest1(int i) {
        mytest1 = i;
    }

    public String getMytest2() {
        return mytest2;
    }

    public void setMytest2(String s) {
        mytest2 = s;
    }

    private int mytest1;
    private String mytest2;
}

 いかがですか? 単純にprivateフィールドの値を読み書きするだけのアクセサですが、ともかくXMLからBeanのプロパティが自動生成されていることが確認できるでしょう。

まとめ

 以上、XMLファイルを読み込み、それをもとにBeanクラスを自動生成する方法について一通り説明を行いました。ここで作成したのは、ごくごく単純なBeanクラスです。このままではそれほど便利とも思えないでしょう。が、ここで行った処理をそのまま修正することで、さらに複雑なメソッド類を組み込めることは誰しも想像がつくはずです。

 Javassistによる「クラスのバイトコードを直接修正する」という手法の魔力を、少しは感じられたのではないでしょうか。既にできあがっているクラスに後から自由にフィールドやメソッドを追加できる。これは、考えようによってはさまざまな応用ができます。

 昨今、「アスペクト指向」的なシステムが次第に増えつつありますが、こうしたシステムでは、複数のクラスにまたがる横断的機能(アスペクト)を自由に組み込めるような設計になっています。この考え方を根底で支えているのが、Javassistに代表される「クラスのバイトコード操作」技術といっていいでしょう。最初から設計されたクラスを作っていくのではなく、必要に応じて後から複数のクラスに必要な機能を組み込んでいく――これは、あなたのシステム設計の考え方を根底から変えることになるかも知れませんよ?

 JavassistとXMLを組み合わせることで、こうしたクラスファイルの改変をより簡単に行えるようになります。この組み合わせは、クラスファイル操作という点では最強ではないか、と思うのですがいかがでしょう。

この記事は参考になりましたか?

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
特集記事連載記事一覧

もっと読む

この記事の著者

掌田 津耶乃(ショウダ ツヤノ)

三文ライター&三流プログラマ。主にビギナーに向けたプログラミング関連の執筆を中心に活動している。※現在、入門ドキュメントサイト「libro」、カード型学習サイト「CARD.tuyano.com」を公開...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/1355 2007/06/11 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー