前ページ

アプリケーションに特有なポイントカット指定子

ポイントカットの宣言は、しばしば脆く、壊れやすいことが知られています。開発者が織り込み対象のクラス定義を修正・変更しようとした場合、そのクラスのジョインポイントを選択しているポイントカットが、壊れてしまうケースがよくあります (fragile pointcut problem)。例えば、ポイントカット call("Rectangle#setX(..)") は、Rectangle クラスに宣言された setX メソッドの呼び出しをジョインポイントとして指定します。もしリファクタリングの影響などにより、この Rectangle クラスの setX メソッドの名前が修正・変更されたとすると、このポイントカットは指定したジョインポイントを選択できなくなってしまいます。

この問題に対し、何人かの研究者は、プログラムのロジックを利用する手法でジョインポイントを指定することを提案しています。私たちもまた、Josh (ジョッシュ) で、そのアイディアを提案しています。Josh は、以前私たちが開発した強力なポイントカット記述力をもつ AOP 言語です。Josh では、利用者が Java で書かれた Josh のプラグインとして、新しいポイントカット指定子を実装することができます。Josh は Java バイトコード変換器 Javassist (www.javassist.org) の上で実装されているため、新しいポイントカット指定子を実装するために、利用者は Javassist が提供している全ての機能を使うことができます。複雑なプログラムのロジックに従ったジョインポイントを指定するためのポイントカット指定子を、実装することが可能になります。このことは、GluonJ も同様です。GluonJ も、Javassist の上で実装されています。もし利用者が Javassist を利用したプログラミングを熟知していれば、複雑なプログラム・ロジックを基にしたポイントカット指定子を、簡単に実装することができます。

updater ポイントカット指定子

以下では、どのように updater ポイントカット指定子を GluonJ に実装するのかを示します。updater ポイントカットは、AOSD 2004 で発表された Josh に関する論文の第 4 章で説明されている例です (この例は AOSD 論文の発展版である日本語論文でも説明されています)。

updater ポイントカット指定子は、クラス名とメソッド名の 2 つの引数をとります。ここでは、そのクラスの名前は Shape であり、メソッドの名前は redraw だとします。Shape もしくはその Shape のサブクラスに宣言されている redraw メソッドの役割は、そのクラスが表現する図形オブジェクトをスクリーン上に描くことです。そのため、redraw メソッドは、そのクラスに宣言されたいくつかのフィールド値を読み出します。例えば、Shape のサブクラスである Line クラスに宣言されている redraw は、フィールド x0, y0, x1, そして y1 の値を読み出します。これらのフィールドの値は、スクリーン上に描かれる Line オブジェクトの場所を決定するために使われます。updater ポイントカット指定子は、これらのフィールドの値を更新したメソッドの呼び出しを指定します。そのような値の更新は、図形オブジェクトの形状 (もしくはスクリーン上の位置) を変更します。それゆえ、updater によって指定されたジョインポイントは、図形オブジェクトの形状を変更するメソッドの呼び出しなのです。

updater ポイントカット指定子の実装

以下に、ユーティリティ・クラス javassist.gluonj.util.SimplePcNode を利用した、updater ポイントカット指定子の実装方法を示します。その実装は単純です (ここでは、updater ポイントカット指定子のソースコードの一部を記載します。完全なソースコードは、javassist/gluonj/plugin/Updater.java です)。

package javassist.gluonj.plugin;

import javassist.*;     // 他の import 文を省略します

@Glue public class Updater {
    @Refine static class Pcd2 extends Pcd {
        public static Pointcut updater(String className, String methodName) {
            Pointcut pc = make();
            return ((Pcut2)pc).updater(className, methodName);
        }
    }

    @Refine static class Pcut2 extends Pointcut {
        public Pcut2() { super(null); }
        public Pointcut updater(String className, String methodName) {
            setTree(new UpdaterPc(className, methodName));
            return this;
        }
    }

    public static class UpdaterPc extends SimplePcNode {
        // 後で改めて示します。
    }
}

@Glue クラス Updater の基本的な構成は、MetaTag の場合と同じです。木のノードを表現する UpdaterPc クラスの定義は、以下のようになります。

    public static class UpdaterPc extends SimplePcNode {
        private ClassPool cpool;
        private String rootClassName;
        private CtClass rootClass;
        private String methodName;
        private HashMap updatersMap;

        public UpdaterPc(String cName, String mName) {
            rootClassName = cName;
            methodName = mName;
            updatersMap = new HashMap();
        }

        /**
         * アドバイスの織り込みを始める前に呼び出されます。
         */
        public void prepare(PatternParser p) throws WeaveException {
            cpool = p.getClassPool();
            try {
                rootClass = cpool.get(rootClassName);
            }
            catch (NotFoundException e) {
                throw new WeaveException(e);
            }
        }

        /**
         * 与えられたクラスに宣言されている updater メソッドを収集します。
         */
        public void inspect(CtClass clazz) throws WeaveException {
            if (!clazz.subclassOf(rootClass))
                return;

            try {
                String className = clazz.getName();
                CtMethod m = clazz.getDeclaredMethod(methodName);
                HashMap fields = accessedFields(className, m);
                HashMap updaters = updateMethods(clazz, fields);
                updatersMap.put(className, updaters);
            }
            catch (CannotCompileException e) {
                throw new WeaveException(e);
            }
            catch (NotFoundException e) {
                return;
            }
        }

        /**
         * 与えられたジョインポイントが、updater メソッドの呼び出しであれば、
         * true を返します。
         */
        public boolean match(MethodCall joinPoint, Residue[] residue)
            throws WeaveException
        {
            String className = joinPoint.getClassName();
            Object updaters = updatersMap.get(className);
            if (updaters == null)
                return false;

            try {
                return ((HashMap)updaters).get(joinPoint.getMethod()) != null;
            }
            catch (NotFoundException e) {
                throw new WeaveException(e);
            }
        }

        // 以下、省略します
    }
}

このクラス内には、重要なメソッドが 3 つあります。それは prepare, inspect, そして match メソッドです。prepare は、GluonJ がアドバイスを織り込み始める前に呼び出されるメソッドです。主に、織り込みのための準備作業を行います。PatternParser オブジェクトは、クラス・パターンやメソッド・パターンを解析するために使われます。

GluonJ がそれぞれのクラスにアドバイスを編み込むたびに、inspect メソッドは呼び出されます。今回の例では、inspect メソッド内にて、与えられたクラスの定義をチェックし、そのクラスに定義されている updater メソッドを収集します。収集された updater メソッドのリストは、後で利用するため updatersMap フィールドに登録しておきます。

match メソッドは、ジョインポイントが見つかったときに呼び出されます。ここでは、match メソッドの引数として与えられたメソッドの呼び出しが、上記の inspect メソッドによって収集された updater メソッドへの呼び出しであるかどうかをチェックします。もし match メソッドの返り値が true (正確には、ポイントカット式全体の値が true) であれば、GluonJ はそのジョインポイントにアドバイスを織り込みます (アドバイス・ボディをインライン展開します)。もし false である場合、GluonJ はそのジョインポイントの箇所では何もしません。

match メソッドは、UpdaterPc クラスのスーパークラスである SimplePcNode から継承されたメソッドです。SimplePcNode には、引数の型が異なる複数の match メソッドがあります。もしこの updater ポイントカット指定子が異なるジョインポイントの型 (フィールド・アクセスなど) を指定する場合、他の match メソッド (match(FieldAccess, Residue[])) をオーバーライドしなければなりません。

updater ポイントカット指定子の使い方

ここでは、updater ポイントカット指定子の使い方を説明していきます。もちろん、上記に示した @Glue クラスを、事前に GluonJ へインストールしておかなければなりません (インストールの方法は、@Glue クラスをインストールするを参考にしてください)。

以下は、@Glue クラスを編み込む対象となるサンプルプログラムです。

package test;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

public class ShapeEditor {
    private List shapes;

    public ShapeEditor() {
        shapes = new LinkedList();
    }

    public void addShape(Shape shape) {
        shapes.add(shape);
    }

    public void paint() {
        Iterator iter = shapes.iterator();
        while (iter.hasNext())
            ((Shape)iter.next()).redraw();
    }

    public static void main(final String[] args) {
        new ShapeEditor().run();
    }

    public void run() {
        Point p = new Point(this, 11, 17);
        p.move(5, 4);
        p.setName("p1");
    }
}

abstract class Shape {
    protected ShapeEditor editor;

    public Shape(ShapeEditor e) {
        editor = e;
        e.addShape(this);
    }

    abstract void redraw();
}

class Point extends Shape {
    private int x;
    private int y;
    private String name;  // redraw メソッド内でアクセスされません

    public Point(ShapeEditor e, int x, int y) {
        super(e);
        this.x = x;
        this.y = y;
    }

    public int getX() { return x; }
    public void setX(int newX) { x = newX; }

    public int getY() { return y; }
    public void setY(int newY) { y = newY; }

    public String getName() { return name; }

    public void setName(String s) {
         System.out.println("setName()");
         name = s;
    }

    public void move(int dx, int dy) {
        System.out.println("move()");
        x += dx;
        y += dy;
    }

    public void redraw() {
        System.out.println("(" + x + ", " + y + ")");
    }
}

Point クラス内で宣言された redraw メソッドは、そのオブジェクトをスクリーン上に描画するため、フィールド xy の値を読み込みます。しかし、フィールド name の値を読むことはありません。それゆえ、もし以下のような @Glue クラスを定義した場合、GluonJ は x もしくは y の値を更新するメソッドの呼び出しを、ジョインポイントとして指定します。

package test;

import javassist.gluonj.*;

@Glue public class UpdateScreen {
    @After("{ paint(); }") 
    Pointcut pc = Pcd.updater("test.Shape", "redraw");
}

ポイントカット pc は、setX, setY, もしくは move メソッドの呼び出しを指定します。setName メソッドの呼び出しは、選択されません。ShapeEditor クラスの main メソッドが実行されたとき、move メソッドの呼び出し箇所でのみ、@After アドバイスのボディが実行されます。ただし、@After アドバイスは、move メソッドの呼び出し側で実行されます。この例の場合、ShapeEditor クラスの run メソッドの内で実行されます。そのため、@After アドバイス・ボディのスコープ内の this オブジェクトは、ShapeEditor オブジェクトであり、paint メソッドは、ShapeEditor オブジェクトの paint メソッドを表します。

前ページ


Copyright (C) 2006 by Shigeru Chiba and Muga Nishizawa. All rights reserved.