4

Digester には、理解できない奇妙な動作があります。

入力xmlで「roles/role」ノードに遭遇するたびに「Role」オブジェクトのコンストラクターを呼び出す次のコードがあります。

        AbstractRulesModule loader = (new AbstractRulesModule() {

        protected void configure() {
            forPattern("roles/role").createObject().ofType(Role.class)
                    .usingConstructor(String.class, String.class).then()
                    .callParam().fromAttribute("machine").ofIndex(0);

            forPattern("roles/role").callParam().fromAttribute("name")
                    .ofIndex(1);

            forPattern("roles/role").setNext("add");

        }
    });

    Digester digester = DigesterLoader.newLoader(loader).newDigester();
    List<Role> roles = new ArrayList<>();

    digester.push(roles);

    digester.parse(new File("c:/RoleMapping.xml"));

    System.out.println(roles);
    System.out.println(Role.count);

Role のコンストラクターが呼び出されるたびに、Role.count がインクリメントされます。奇妙なことに、次の xml に対して上記のコードを実行した後、Role.count は 1 ではなく 2 です。コードをデバッグすると、Digester がコンストラクター パラメーターとして "null" を使用して 2 つの余分なオブジェクトを作成しようとしたようです。

<roles>
    <role name="m1" machine="mymachine" />
</roles>

コンストラクターの引数が null であるかどうかをチェックするコードがある場合、これはあらゆる種類の問題につながります。

私の Role クラスの定義は次のとおりです。

public class Role {

    private String machine;
    private String name;

    static int count = 0;

    public Role(String machine, String name)  {
        this.machine = machine;
        this.name = name;
        count++;
    }
}
4

1 に答える 1

0

質問は3年前のものですが、最近同じことに出くわしましたが、答えはまだ有効です...

コンストラクターが 2 回呼び出される理由は、Digester 3 がパラメーターを使用してコンストラクターを処理する方法です。Digester の問題は鶏が先か卵が先かという問題です...必要なパラメーターがすべて揃うまでコンストラクターを呼び出すことはできませんが、callParamルールは子要素からデータを取得できるため、完全に完了するまで子要素をすべて持つことはできません。要素を処理しました。

あなたの場合、すべてのパラメーターは属性で使用できますが、XML を次のように変更したかどうかを検討してください。

<roles>
    <role>
        <name>m1</name>
        <machine>mymachine</machine>
    </role>
</roles>

あるいは:

<roles>
    <role>
        <name>m1</name>
        <machine>mymachine</machine>
        <another>
            <tag>which</tag>
            <does>morestuff</does>
            ...
        </another>
    </role>
</roles>

call param ルールは子データのどこでも呼び出される可能性があるため、ダイジェスターは と の間で発生するすべてを効果的に記憶する必要が<role>あり、オブジェクトを作成する前にこれらすべてを実行する必要があります。</role>

これを行うために、ダイジェスターは、構築されるクラス (Role) の周りにプロキシ ラッパーを作成し、すべてのコンストラクター引数に対して null を渡すダミー インスタンスを作成してから、メイン要素の子に対してトリガーされる他のすべてのメソッドを呼び出します。プロキシ クラスは、これらのメソッド呼び出しをインターセプトし、(パラメーターを含めて) 記録し、ダミー インスタンスに渡します。終了要素タグに到達すると、ダミー オブジェクトが破棄され、実際のコンストラクター パラメーターを使用して新しいオブジェクトが作成され、記録されたすべてのメソッド呼び出しが新しいオブジェクトに「再生」されます。

お気づきのように、これはオブジェクトを 2 回作成するだけでなく、ダイジェスター ルールによってトリガーされるすべてのメソッドを 2 回呼び出します。1 回は「記録」フェーズで、1 回は「再生」フェーズです。

これはすべて、単純なデータ オブジェクトでは正常に機能しますが、より複雑なデータ オブジェクトを作成すると、奇妙な結果になる可能性があります。例については、この消化チケットを参照してください。

usingDefaultConstructorArgumentsnull ポインター例外を回避するために、ルールを使用して、デフォルトのコンストラクター パラメーターに使用する値をダイジェスターに伝えることができます。

forPattern("roles/role").createObject().ofType(Role.class)
    .usingConstructor(String.class, String.class).then()
    .usingDefaultConstructorArguments("one", "two").then()
    .callParam().fromAttribute("machine").ofIndex(0);

より複雑なケースの場合、またはアプローチを好む場合は、ビルダー クラスとカスタム ルールを使用できます。基本的な考え方は、要素に到達したときに、要素の終了タグでトリガーされるカスタム ルールと共にビルダー クラスをスタックにプッシュすることです。要素本体の処理中に、ダイジェスターはビルダー クラスにデータを渡す通常のルールとしてすべてのルールを呼び出します。終了タグでカスタム ルールがトリガーされ、ビルダーを呼び出してオブジェクトをビルドし、ダイジェスター スタックのビルダー オブジェクトをビルドされたオブジェクトに置き換えます。これにはカスタム ビルダー クラスが必要ですが、思ったよりずっと簡単です。実際の例については、このダイジェスター チケットを参照してください。

これで謎が解けるといいな!

于 2015-04-04T08:13:37.017 に答える