Digester 3 calls constructors twice when creating objects

In Digester, weird behavior that I canโ€™t wrap my head around.

I have the following code that calls the constructor of the Role object whenever it encounters a "role / role" node in the xml input file:

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); 

Each time the role constructor is called, Role.count is incremented. Oddly enough, after running the above code using the following xml, Role.count is 2 instead of 1. When I debug the code, it seems like Digester tried to create 2 additional objects with "zero" as constructor parameters.

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

This will lead to any problem if I have a code check if the constructor arguments are null.

The definition of my Role class is:

 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
source share
1 answer

I see that the question is 3 years old, but recently I came across this, and the answer is still valid ...

The reason the constructor is called twice is because Digester 3 handles constructors with parameters. The problem for Digester is the chicken and the egg ... it cannot call the constructor until it has all the necessary parameters, but since the callParam rules can get its data from the children, it does not have all the children until it is completely processed the item.

In your case, all parameters are available in the attributes, but think about whether you changed your XML to:

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

Or even:

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

The vakuoner should effectively remember everything that happens between <role> and </role> , since the call parameter rules can be called anywhere in the child data, and he must do all this before creating the object.

To do this, digester creates a proxy wrapper around the created class (Role), creates a dummy instance that passes null for all arguments to the constructor, and then calls all other methods that are launched for the children of the main element. The proxy class intercepts these method calls, writes them (including parameters), and passes them to the dummy instance. As soon as the end element tag is reached, the dummy object will be discarded, a new one will be created with real constructor parameters, and all recorded method calls will be โ€œreplayedโ€ back to the new object.

As you noticed, this not only creates the object twice, but also calls all the methods triggered by the digester rules twice: once during the recording phase and once during the playback phase.

All this works great for simple data objects, but can have strange consequences when building more complex ones. See this diver ticket for an example.

To avoid exceptions to null pointer exceptions, you can tell the digest which values โ€‹โ€‹to use for standard constructor parameters using the usingDefaultConstructorArguments rule:

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

For more complex cases or just if you prefer the approach, you can use the builder class and custom rule. The main idea is that you get the element that you click on the builder class onto the stack, as well as a custom rule that is triggered in the end tag of the element. When processing the body of an element, digester calls all the rules like ordinary data transfers to the builder class. At the end of the tag, a custom rule is launched, which calls the builder to build the object, and then replaces the builder object on the drive stack with the built-in object. This requires a special builder class, but it is much simpler than it sounds. See this ticket for divers for a working example.

Hope this clears the secret!

0
source

Source: https://habr.com/ru/post/1386799/


All Articles