Add the missing property to the groovy @Canonical bean constructor call?

I am new to groovy and have just started exploring its metaprogramming capabilities. I am stuck in adding missing properties to a bean constructor call.

In the class that will be used with FactoryBuilderSupport, I want to dynamically add those properties that are not yet defined and provided during the constructor call. Here is a stripped down version:

@Canonical class MyClass { def startDate def additionalProperties = [:] def void propertyMissing(String name, value) { additionalProperties[name] = value } } 

However, if I create a class with unknown properties, the property is not added, but a MissingPropertyException obtained MissingPropertyException :

 def thing = new MyClass(startDate: DateTime.now(), duration: 1234) 

The duration of the property does not exist, and I expected it to be handled using propertyMissing . As far as I understand groovy, calling tuple-constructor leads to calling the constructor without an argument, followed by calls to the groovy-generated setters. So why am I getting a MissingPropertyException ?

Since I'm new to groovy, I'm probably missing out on some basic AST or MOP rules. I would really appreciate your help.

+5
source share
1 answer

If you use @Canonical and you define a first-class object with def , as you do with startDate , the annotation generates the following constructors:

 @Canonical class MyClass { def startDate def additionalProperties = [:] def void propertyMissing(String name, value) { additionalProperties[name] = value } } // use reflection to see the constructors MyClass.class.getConstructors() 

Created constructors:

 public MyClass() public MyClass(java.lang.Object) public MyClass(java.util.LinkedHashMap) public MyClass(java.lang.Object,java.lang.Object) 

In the @Canonical documentation you can see the following limitation:

Groovy normal map-style naming conventions will not be available if the first property has a LinkedHashMap type or if there is one Map, AbstractMap, or HashMap property

Due to public MyClass(java.util.LinkedHashMap) you cannot use tuple-constructor and you will get MissingPropertyException .

Surprisingly, if you define your first object (note that I say first ) with type instead of using def , the @Canonical annotation does not add public MyClass(java.util.LinkedHashMap) and then your tuple-constructor works, see the following code :

 @Canonical class MyClass { java.util.Date startDate def additionalProperties = [:] def void propertyMissing(String name, value) { additionalProperties[name] = value } } // get the constructors MyClass.class.getConstructors() // now your code works def thing = new MyClass(startDate: new java.util.Date(), duration: 1234) 

Now created constructors:

 public MyClass() public MyClass(java.util.Date) public MyClass(java.util.Date,java.lang.Object) 

Since there is no public MyClass(java.util.LinkedHashMap) , the restriction does not apply, and you make a call to tuple-constructor .

In addition, I want to say that since this solution works, I canโ€™t argue why ... I read the @Canonical documentation again and again, and I donโ€™t see the part that describes this behavior, so I donโ€™t , why this works, I also make some attempts, and I'm a little confused only when the first def element is created public MyClass(java.util.LinkedHashMap) , i.e.:

 @Canonical class MyClass { def a int c } // get the constructors MyClass.class.getConstructors() 

The first object defined as def ...

 public MyClass() public MyClass(java.lang.Object) public MyClass(java.util.LinkedHashMap) // first def... public MyClass(java.lang.Object,int) 

Now, if I change the order:

 @Canonical class MyClass { int c def a } // get the constructors MyClass.class.getConstructors() 

Now, at first, not def and public MyClass(java.util.LinkedHashMap) not generated:

 public MyClass() public MyClass(int) public MyClass(int,java.lang.Object) 

Hope this helps,

+1
source

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


All Articles