[英]Prevent writing default attribute values JAXB
我正在尝试编写类,以便在JAXB中编写具有属性的元素。 在此XML中,有一些默认值,无论它们是String,int还是自定义类类型。
以下是缩减示例:
@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(name = "FIELD")
public class TestLayoutNode
{
// I want to not write this to the xml when it is 0
@XmlAttribute(name = "num")
private int number;
// I want to not write this when it is "default"
@XmlAttribute(name = "str")
private String str;
}
按照JAXB的规定,避免保存默认值,我知道是否不想编写String,我可以将getter / setter修改为null,如果它读为null,则可以读取默认值。
但是,使用int我不确定该怎么做,因为除非明确更改,否则它将始终具有值0。
有没有更好的方法可以做到这一点? 我可以将内部数据类型更改为String,然后在需要时将其强制转换,但这有点混乱。
您可以通过将字段更改为默认情况下的对象类型来执行以下操作:空值不会出现在XML表示形式中),并在getter中放入一些逻辑:
@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(name = "FIELD")
public class TestLayoutNode
{
@XmlAttribute(name = "num")
private Integer number;
@XmlAttribute
private String str;
public int getNumber() {
if(null == number) {
return 0;
} else {
return number;
}
}
public void setNumber(int number) {
this.number = number;
}
public String getStr() {
if(null == str) {
return "default";
} else {
return str;
}
}
public void setStr(String str) {
this.str = str;
}
}
如果要允许set操作将属性返回其默认状态,则需要在set方法中添加逻辑。
public void setNumber(int number) {
if(0 == number) {
this.number = null;
} else {
this.number = number;
}
}
或者,您可以提供一个未设置的方法:
public void unsetNumber() {
this.number = null;
}
null
如果要允许将str
属性设置为null
以便get方法将返回null
而不是"default"
则可以维护一个标志来跟踪是否已设置它:
private strSet = false;
public String getStr() {
if(null == str && !strSet) {
return "default";
} else {
return str;
}
}
public void setStr(String str) {
this.str = str;
this.strSet = true;
}
Blaise,您不认为解决方案很冗长吗?
是
我的意思是,这种用例可能应该由框架支持。 例如,使用@DefaultValue之类的注释。
JAXB如何今天支持默认值
如果XML中不存在节点,则不会在Java对象中的相应字段/属性上执行一组操作。 这意味着您将属性初始化为的任何值仍然存在。 在元帅上,由于值已填充,因此将被整理。
真正要求的是什么
真正要求的是在具有默认值时不要封送字段/属性。 这样,您希望元数据行为与空值和默认值相同。 这引入了一些要解决的问题:
人们今天在做什么?
通常,对于这种用例,人们只是将int
属性更改为Integer
并将null
为默认值。 我之前没有遇到有人要求这种行为的String
。
您可以更改为整数
private Integer number;
然后,该对象的值在未实例化时将为null。
使用Integer
而不是原始int
。 将所有原始类型替换为其对应的对象,然后可以使用NULL
。
根据字符串默认值,使用并修改getter
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name = "FIELD")
public class NullAttrs {
private Integer number;
private String str;
public void setNumber(Integer number) {
this.number = number;
}
@XmlAttribute(name = "num")
public Integer getNumber() {
return number;
}
public void setStr(String str) {
this.str = str;
}
@XmlAttribute(name = "str")
public String getStr() {
if (str != null && str.equalsIgnoreCase("default"))
return null;
else if (str == null)
return "default";
else
return str;
}
public static void main(String[] args) throws JAXBException {
JAXBContext jc = JAXBContext.newInstance(NullAttrs.class);
NullAttrs root = new NullAttrs();
root.setNumber(null);
root.setStr("default");
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(root, System.out);
}
}
在这种情况下,结果为空FIELD
:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<FIELD/>
尽管它并不像人们希望的那么简洁,但是可以创建XmlAdapters以避免编组默认值。
用例是这样的:
@XmlRootElement(name = "FIELD")
public class TestLayoutNode
{
@XmlAttribute(name = "num")
@XmlJavaTypeAdapter(value = IntegerZero.class, type = int.class)
public int number;
@XmlAttribute(name = "str")
@XmlJavaTypeAdapter(StringDefault.class)
public String str = "default";
}
这是适配器。
整数零:
public class IntegerZero extends DefaultValue<Integer>
{
public Integer defaultValue() { return 0; }
}
字符串默认值:
public class StringDefault extends DefaultValue<String>
{
public String defaultValue() { return "default"; }
}
DefaultValueAdapter:
public class DefaultValue<T> extends XmlAdapter<T, T>
{
public T defaultValue() { return null; }
public T marshal(T value) throws Exception
{
return (value == null) || value.equals(defaultValue()) ? null : value;
}
public T unmarshal(T value) throws Exception
{
return value;
}
}
使用少量不同的默认值,此方法效果很好。
我发现使用自定义getter / setter或适配器的解决方案非常烦人,因此我选择了另一种解决方案:编组器,检查值并将其默认为空。
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Set;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.PropertyException;
import javax.xml.bind.helpers.AbstractMarshallerImpl;
import javax.xml.transform.Result;
import com.google.common.collect.ImmutableSet;
class MyJaxbMarshaller extends AbstractMarshallerImpl {
/** See https://docs.oracle.com/cd/E13222_01/wls/docs103/webserv/data_types.html#wp221620 */
private static final Set<String> SUPPORTED_BASIC_TYPES = ImmutableSet.of(
"boolean", "java.lang.Boolean", "byte", "java.lang.Byte", "double", "java.lang.Double",
"float", "java.lang.Float", "long", "java.lang.Long", "int", "java.lang.Integer",
"javax.activation.DataHandler", "java.awt.Image", "java.lang.String",
"java.math.BigInteger", "java.math.BigDecimal", "java.net.URI", "java.util.Calendar",
"java.util.Date", "java.util.UUID", "javax.xml.datatype.XMLGregorianCalendar",
"javax.xml.datatype.Duration", "javax.xml.namespace.QName",
"javax.xml.transform.Source", "short", "java.lang.Short");
private final Marshaller delegate;
MyJaxbMarshaller(Marshaller delegate) {
this.delegate = delegate;
}
@Override
public void setProperty(String name, Object value) throws PropertyException {
super.setProperty(name, value);
delegate.setProperty(name, value);
}
@Override
public void marshal(Object jaxbElement, Result result) throws JAXBException {
try {
delegate.marshal(clearDefaults(jaxbElement), result);
} catch (ReflectiveOperationException ex) {
throw new JAXBException(ex);
}
}
private Object clearDefaults(Object element) throws ReflectiveOperationException {
if (element instanceof Collection) {
return clearDefaultsFromCollection((Collection<?>) element);
}
Class<?> clazz = element.getClass();
if (isSupportedBasicType(clazz)) {
return element;
}
Object adjusted = clazz.getConstructor().newInstance();
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
copyOrRemove(field, element, adjusted);
}
return adjusted;
}
private Object clearDefaultsFromCollection(Collection<?> collection)
throws ReflectiveOperationException {
@SuppressWarnings("unchecked")
Collection<Object> result = collection.getClass().getConstructor().newInstance();
for (Object element : collection) {
result.add(clearDefaults(element));
}
return result;
}
private static boolean isSupportedBasicType(Class<?> clazz) {
return SUPPORTED_BASIC_TYPES.contains(clazz.getName());
}
private void copyOrRemove(Field field, Object element, Object adjusted)
throws ReflectiveOperationException {
Object value = field.get(element);
if (value != null) {
if (value.equals(field.get(adjusted))) {
value = null;
} else {
value = clearDefaults(value);
}
}
field.set(adjusted, value);
}
}
这适用于像
@XmlRootElement
public class Foo {
@XmlAttribute public Integer intAttr = 0;
@XmlAttribute public String strAttr = "default";
}
您可以根据需要使此方法更加灵活,例如,可以使用注释来标记要在默认情况下忽略的属性,或者可以扩展类以了解@XmlTransient
或方法访问器(两者都不是)现在是我项目中的一个问题)。
为简化绑定类而付出的代价是,封送整理器将创建要封送的对象的深层副本,并与默认值进行大量比较以确定要舍弃的内容。 因此,如果运行时性能对您来说是个问题,那么这可能是徒劳的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.