Je vois que la question date de 3 ans, mais je suis tombé sur la même chose récemment et la réponse est toujours valable...
La raison pour laquelle le constructeur est appelé deux fois est la façon dont Digester 3 gère les constructeurs avec des paramètres. Le problème pour Digester est celui de la poule et de l'oeuf... il ne peut pas appeler le constructeur tant qu'il n'a pas tous les paramètres requis, mais parce que les paramètres ne sont pas disponibles, Digester ne peut pas appeler le constructeur. callParam
Les règles peuvent obtenir leurs données à partir d'éléments enfants, mais elles ne disposent pas de tous les éléments enfants tant qu'elles n'ont pas entièrement traité l'élément.
Dans votre cas, tous les paramètres sont disponibles dans les attributs, mais considérez que si vous modifiez votre XML en :
<roles>
<role>
<name>m1</name>
<machine>mymachine</machine>
</role>
</roles>
Ou même :
<roles>
<role>
<name>m1</name>
<machine>mymachine</machine>
<another>
<tag>which</tag>
<does>morestuff</does>
...
</another>
</role>
</roles>
Le digesteur doit effectivement se souvenir de tout ce qui se passe entre <role>
y </role>
Les règles de paramètre d'appel peuvent être appelées n'importe où dans les données enfant, et tout cela doit être fait avant la création de l'objet.
Pour ce faire, le digesteur crée une enveloppe de proxy autour de la classe à construire (Role), crée une instance fictive en passant null pour tous les arguments du constructeur, puis appelle toutes les autres méthodes déclenchées pour les enfants de l'élément principal. La classe proxy intercepte ces appels de méthode, les enregistre (y compris les paramètres) et les transmet à l'instance fictive. Une fois que la balise de fin d'élément est atteinte, l'objet factice est abandonné, un nouvel objet est créé avec les vrais paramètres du constructeur, et tous les appels de méthode enregistrés sont "rejoués" vers le nouvel objet.
Comme vous l'avez remarqué, non seulement l'objet est créé deux fois, mais toutes les méthodes déclenchées par les règles du digesteur sont appelées deux fois : une fois pendant la phase d'enregistrement et une fois pendant la phase de lecture.
Tout cela fonctionne bien pour les objets de données simples, mais peut avoir des conséquences étranges lors de la construction d'objets plus complexes. Voir ce ticket de digestion pour un exemple.
Pour éviter les exceptions liées aux pointeurs nuls, vous pouvez indiquer au digesteur les valeurs à utiliser pour les paramètres par défaut du constructeur à l'aide de l'élément usingDefaultConstructorArguments
règle :
forPattern("roles/role").createObject().ofType(Role.class)
.usingConstructor(String.class, String.class).then()
.usingDefaultConstructorArguments("one", "two").then()
.callParam().fromAttribute("machine").ofIndex(0);
Pour les cas plus compliqués, ou simplement si vous préférez cette approche, vous pouvez utiliser une classe de construction et une règle personnalisée. L'idée de base est que, lorsque vous arrivez à l'élément, vous poussez une classe de construction sur la pile avec une règle personnalisée déclenchée sur la balise de fin de l'élément. Pendant le traitement du corps de l'élément, le digesteur appelle toutes les règles comme d'habitude en passant les données à la classe de construction. À la balise de fin, la règle personnalisée est déclenchée et invoque le constructeur pour construire l'objet, puis remplace l'objet constructeur sur la pile du digesteur avec l'objet construit. Cela nécessite une classe de constructeur personnalisée, mais c'est beaucoup plus simple qu'il n'y paraît. Voir ce ticket de digestion pour un exemple concret.
J'espère que cela éclaircira le mystère !