Digester 3在创建对象时调用构造函数两次

时间:2011-12-17 04:50:53

标签: java apache-commons-digester

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是2而不是1.当我调试代码时,似乎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++;
    }
}

1 个答案:

答案 0 :(得分:0)

我看到这个问题是3岁了,但我最近遇到了同样的事情,答案仍然有效......

构造函数被调用两次的原因是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>
消化器实际上必须记住<role></role>之间发生的所有事情,因为调用参数规则可以在子数据中的任何地方调用,并且它必须在创建之前完成所有这些操作。对象

为此,浏览器在要构造的类(Role)周围创建一个代理包装器,创建一个为所有构造函数参数传递null的虚拟实例,然后调用为主元素的子元素触发的所有其他方法。代理类拦截这些方法调用,记录它们(包括参数),并将它们传递给虚拟实例。一旦到达结束元素标记,就丢弃虚拟对象,用真实构造函数参数创建一个新对象,并且所有记录的方法调用都被重放&#39;回到新对象。

正如您所注意到的,这不仅会创建对象两次,而且还会将消化器规则触发的所有方法调用两次:一次在录制过程中。阶段,以及“播放”期间的一次相。

这一切都适用于简单的数据对象,但在构造更复杂的数据对象时可能会产生奇怪的后果。有关示例,请参阅this digester ticket

为了避免空指针异常,您可以使用usingDefaultConstructorArguments规则告诉消化器哪些值用于默认构造函数参数:

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

对于更复杂的情况,或者只是您更喜欢该方法,您可以使用构建器类和自定义规则。基本思想是当你到达元素时,将构建器类推送到堆栈以及在元素结束标记上触发的自定义规则。在处理元素主体时,消化器将所有规则调用为正常传递数据到构建器类。在结束标记处,触发自定义规则,该规则调用构建器以构建对象,然后使用构建的对象替换消化堆栈上的构建器对象。这确实需要一个自定义构建器类,但比听起来简单得多。有关工作示例,请参阅this digester ticket

希望这能揭开神秘面纱!