可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
When I generate JAXB classes from an XSD, the elements with maxOccurs="unbounded"
gets a getter method generated for them, but no setter method, as follows:
/**
* Gets the value of the element3 property.
*
* <p>
* This accessor method returns a reference to the live list,
* not a snapshot. Therefore any modification you make to the
* returned list will be present inside the JAXB object.
* This is why there is not a <CODE>set</CODE> method for the element3 property.
*
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getElement3().add(newItem);
* </pre>
*
*
* <p>
* Objects of the following type(s) are allowed in the list
* {@link Type }
*
*
*/
public List<Type> getElement3() {
if (element3 == null) {
element3 = new ArrayList<Type>();
}
return this.element3;
}
The method comment makes it crystal clear on how can I use it, but my question is as follows:
Why doesn't JAXB just generate a setter, following the Java Beans rules? I know I can write the setter method myself, but is there any advantage to the approach suggested in the generated getter method?
This is my XSD:
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://www.example.org/DoTransfer/" targetNamespace="http://www.example.org/DoTransfer/">
<element name="CollectionTest" type="tns:CollectionTest"></element>
<complexType name="CollectionTest">
<sequence>
<element name="element1" type="string" maxOccurs="1" minOccurs="1"></element>
<element name="element2" type="boolean" maxOccurs="1" minOccurs="1"></element>
<element name="element3" type="tns:type" maxOccurs="unbounded" minOccurs="1" nillable="true"></element>
</sequence>
</complexType>
<complexType name="type">
<sequence>
<element name="subelement1" type="string" maxOccurs="1" minOccurs="1"></element>
<element name="subelement2" type="string" maxOccurs="1" minOccurs="0"></element>
</sequence>
</complexType>
</schema>
回答1:
Here is the justification from the JAXB specification - page 60.
Design Note – There is no setter method for a List property. The
getter returns the List by reference. An item can be added to the
List returned by the getter method using an appropriate method
defined on java.util.List. Rationale for this design in JAXB 1.0 was
to enable the implementation to wrapper the list and be able to
perform checks as content was added or removed from the List.
So if the implementation of the List was overriding add/remove to perform validation, replacing that 'special' List with (for instance) an ArrayList would defeat these checks.
回答2:
Link for : No setter for list
The code in the getter method ensures that the List is
created. There is no corresponding setter which means that all
additions or deletions of list elements have to be made on the "live"
list.
As the quote says that there is no setter as when you use the getter method it insures that a new instance of the list is initialized if not present.
And after that when you have to add or remove anything you will have to use
getElement3().add(Type);
UPDATE : Difference in marshalling for null
and empty list
Example where list is null
@XmlRootElement(name = "list-demo")
public class ListDemo {
@XmlElementWrapper(name = "list")
@XmlElement(name = "list-item")
private List<String> list;
}
OUTPUT will be
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<list-demo/>
Example where list is empty
@XmlRootElement(name = "list-demo")
public class ListDemo {
@XmlElementWrapper(name = "list")
@XmlElement(name = "list-item")
private List<String> list = new ArrayList<String>();
}
OUTPUT will be:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<list-demo>
<list/>
</list-demo>
回答3:
Agree with Patrick's concern above. If I was coding to the generated java classes directly I'd be happy to oblige, but I'm using an introspective tool expects either a setter or a directly accessible member.
Had success using a plugin to XJC from
https://github.com/highsource/jaxb2-basics/wiki/JAXB2-Setters-Plugin
and adding a -B-Xsetter argument to wsimport
回答4:
In case anyone is here looking for a way to get rid of those annoying lazy initializers in XJC-generated code... In my Maven POM, this goes under <build><plugins>
:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<executions>
<execution>
<id>remove-jaxb-generated-lazy-initializers</id>
<phase>process-sources</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target if="${remove-jaxb-generated-lazy-initializers}">
<echo message="Running 'replaceregexp' target on generated sources..."/>
<echo message="This removes JAXB-generated lazy initializers from collection accessors."/>
<replaceregexp match="if \([\w_]+ == null\) \{\s+[\w_]+ = new ArrayList<[\w_]+>\(\);\s+\}\s+" replace="" flags="g">
<fileset dir="${project.build.directory}/generated-sources" includes="**/*.java"/>
</replaceregexp>
</target>
</configuration>
</execution>
</executions>
</plugin>
For setters, I'm also adding @lombok.Setter
to certain classes using org.jvnet.jaxb2_commons:jaxb2-basics-annotate
and a bindings file. Thus the classes end up being standard beans.
I would love to hear it if anyone knows of a less hacky way--e.g., a XJC plugin.
回答5:
One can write their own XJC plugin
for their specific requirement.
In this example one is trying to add an id
field of type long
in every generated java file from xjc
:
package com.ricston;
import java.io.IOException;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import com.sun.codemodel.JBlock;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JFieldVar;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JMod;
import com.sun.codemodel.JType;
import com.sun.codemodel.JVar;
import com.sun.tools.xjc.BadCommandLineException;
import com.sun.tools.xjc.Options;
import com.sun.tools.xjc.Plugin;
import com.sun.tools.xjc.outline.ClassOutline;
import com.sun.tools.xjc.outline.Outline;
public class XJCPlugin extends Plugin {
public final static String ID = "id";
public final static JType LONG_TYPE = new JCodeModel().LONG;
public final static String ID_GETTER = "getId";
public final static JType VOID_TYPE = new JCodeModel().VOID;
public final static String ID_SETTER = "setId";
@Override
public String getOptionName() {
return "Xexample-plugin";
}
@Override
public int parseArgument(Options opt, String[] args, int i)
throws BadCommandLineException, IOException {
return 1;
}
@Override
public String getUsage() {
return " -Xexample-plugin : xjc example plugin";
}
@Override
public boolean run(Outline model, Options opt, ErrorHandler errorHandler)
throws SAXException {
for (ClassOutline classOutline : model.getClasses()) {
JFieldVar globalId = classOutline.implClass.field(JMod.PRIVATE,
LONG_TYPE, ID);
JMethod idGetterMethod = classOutline.implClass.method(JMod.PUBLIC,
LONG_TYPE, ID_GETTER);
JBlock idGetterBlock = idGetterMethod.body();
idGetterBlock._return(globalId);
JMethod idSetterMethod = classOutline.implClass.method(JMod.PUBLIC,
VOID_TYPE, ID_SETTER);
JVar localId = idSetterMethod.param(LONG_TYPE, "_" + ID);
JBlock idSetterBlock = idSetterMethod.body();
idSetterBlock.assign(globalId, localId);
}
return true;
}
}
Full example here.
Another example here:
https://blog.jooq.org/2018/02/19/how-to-implement-your-own-xjc-plugin-to-generate-tostring-equals-and-hashcode-methods/
Their are plugins available for generating hashCode
, equals
, setters-for-list
at github too.
References:
Answer to a question I had asked.