繁体   English   中英

防止编写默认属性值JAXB

[英]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;
}

允许Set为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对象中的相应字段/属性上执行一组操作。 这意味着您将属性初始化为的任何值仍然存在。 在元帅上,由于值已填充,因此将被整理。

真正要求的是什么

真正要求的是在具有默认值时不要封送字段/属性。 这样,您希望元数据行为与空值和默认值相同。 这引入了一些要解决的问题:

  1. 您现在如何将null编组为XML? 默认情况下,它是否仍编组为缺少的节点?
  2. 是否需要提供一种机制来区分属性是默认值(在XML中不存在)还是已设置为与默认值相同(在XML中存在)?

人们今天在做什么?

通常,对于这种用例,人们只是将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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM