4

Digester 中有一个奇怪的行为,我无法理解。

我有以下代码在输入 xml 中遇到“角色/角色”节点时调用“角色”对象的构造函数:

        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 试图创建 2 个额外的对象,并将“null”作为构造函数参数。

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

如果我有代码检查构造函数的参数是否为空,这将导致各种问题。

我的角色类的定义是:

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 岁,但我最近遇到了同样的事情,答案仍然有效......

构造函数被调用两次的原因是 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>,因为调用参数规则可以在子数据中的任何地方调用,并且它必须在创建对象之前完成所有这些。

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

正如您所注意到的,这不仅创建了两次对象,而且调用了由消化器规则触发的所有方法两次:一次在“录制”阶段,一次在“回放”阶段。

这一切都适用于简单的数据对象,但在构建更复杂的数据对象时可能会产生奇怪的后果。有关示例,请参见此消化器票。

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

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 回答