Digester 3 дважды вызывает конструкторов при создании объектов - PullRequest
4 голосов
/ 17 декабря 2011

В Дигестере есть странное поведение, из-за которого я не могу обернуться.

У меня есть следующий код, который вызывает конструктор объекта «Роль» всякий раз, когда он встречает узел «role / role» во входном 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.count увеличивается. Как ни странно, после запуска приведенного выше кода для следующего XML-файла Role.count равен 2 вместо 1. Когда я отлаживаю код, создается впечатление, что Digester попытался создать 2 дополнительных объекта с «нулем» в качестве параметров конструктора.

<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++;
    }
}

1 Ответ

0 голосов
/ 04 апреля 2015

Я вижу, что вопросу 3 года, но я недавно столкнулся с тем же самым, и ответ все еще действителен ...

Причина, по которой конструктор вызывается дважды, заключается в том, что Digester 3 обрабатывает конструкторы с параметрами. Проблема для Дигестера заключается в том, что он не имеет ничего общего ... он не может вызвать конструктор, пока у него не будут все необходимые параметры, но поскольку правила 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 для всех аргументов конструктора, а затем вызывает все другие методы, запускаемые для дочерних элементов основного элемента. Прокси-класс перехватывает эти вызовы методов, записывает их (включая параметры) и передает их в фиктивный экземпляр. По достижении тега конечного элемента фиктивный объект отбрасывается, создается новый объект с реальными параметрами конструктора, и все записанные вызовы методов «воспроизводятся» обратно в новый объект.

Как вы заметили, это не только создает объект дважды, но и вызывает все методы, запускаемые правилами метантенка дважды: один раз во время фазы записи и один раз во время фазы воспроизведения.

Все это прекрасно работает для простых объектов данных, но может иметь странные последствия при построении более сложных объектов. См. этот билет для варочного котла для примера.

Чтобы просто избежать исключений нулевого указателя, вы можете указать методу, какие значения использовать для параметров конструктора по умолчанию, используя правило usingDefaultConstructorArguments:

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

Для более сложных случаев или просто, если вы предпочитаете подход, вы можете использовать класс построителя и пользовательское правило. Основная идея заключается в том, что, когда вы попадаете в элемент, вы помещаете класс компоновщика в стек вместе с настраиваемым правилом, запускаемым в конце тега элемента. При обработке тела элемента, дайджест вызывает все правила как обычную передачу данных в класс компоновщика. В конце тега запускается пользовательское правило, которое вызывает конструктор для создания объекта, а затем заменяет объект компоновщика в стеке метантенка на встроенный объект. Для этого нужен собственный класс строителя, но он намного проще, чем кажется. См. рабочий билет для рабочего примера.

Надеюсь, это прояснит тайну!

...