JAXB和构造函数

30
我正在开始学习JAXB,所以我的问题可能很幼稚。现在我有一些类,并想生成XML模式。按照 这个 指示进行操作后,我得到了异常:

IllegalAnnotationExceptions ...没有无参默认构造函数。

是的,我的类没有默认的无参构造函数。这太容易出错了。我的类有package可见的构造函数/最终方法和参数,我该怎么办 - 创建一些特定的momemto/builder类或将我的构造函数指定给JAXB(以什么方式?)?谢谢。

4个回答

42

JAXB可以通过使用XML适配器来支持这种情况。 假设您有以下没有零参数构造函数的对象:

package blog.immutable;

public class Customer {

    private final String name;
    private final Address address;

    public Customer(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    public String getName() {
        return name;
    }

    public Address getAddress() {
        return address;
    }

}

你只需要创建一个可以被映射的这个类的版本:

package blog.immutable.adpater;

import javax.xml.bind.annotation.XmlAttribute;
import blog.immutable.Address;

public class AdaptedCustomer {

    private String name;
    private Address address;

    @XmlAttribute
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

}

还需要一个XML适配器来在它们之间进行转换:

package blog.immutable.adpater;

import javax.xml.bind.annotation.adapters.XmlAdapter;
import blog.immutable.Customer;

public class CustomerAdapter extends XmlAdapter<AdaptedCustomer, Customer> {

    @Override
    public Customer unmarshal(AdaptedCustomer adaptedCustomer) throws Exception {
        return new Customer(adaptedCustomer.getName(), adaptedCustomer.getAddress());
    }

    @Override
    public AdaptedCustomer marshal(Customer customer) throws Exception {
        AdaptedCustomer adaptedCustomer = new AdaptedCustomer();
        adaptedCustomer.setName(customer.getName());
        adaptedCustomer.setAddress(customer.getAddress());
        return adaptedCustomer;
    }

}

对于涉及到Customer类的属性,只需使用@XmlJavaTypeAdapter注释:

package blog.immutable;

import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import blog.immutable.adpater.CustomerAdapter;

@XmlRootElement(name="purchase-order")
public class PurchaseOrder {

    private Customer customer;

    @XmlJavaTypeAdapter(CustomerAdapter.class)
    public Customer getCustomer() {
        return customer;
    }

    public void setCustomer(Customer customer) {
        this.customer = customer;
    }

} 

更详细的示例请参见:


3
请不要直接复制链接,用你的回答解释基本内容,如果需要更多的解释再提供链接。这样,即使链接失效,我们仍然有答案。 - Valentin Rocher
2
我同意不仅仅复制链接。这个答案是我在通勤途中用手机发布的。等我到电脑前会提供更多细节。如果有帮助,链接是指向我的博客的。 - bdoughan
1
承诺之后,现在我回到电脑前,已经更新了我的答案,并提供了一个完整的示例来演示解决方案。或许那个投反对票的人会考虑改变他们的投票? - bdoughan
是的,抱歉,轮到我离开电脑了 ;) - Valentin Rocher
哇,那真的很“简单”!我建议使用这个技巧代替:https://dev59.com/Dm855IYBdhLWcg3wXTAI#6700837 - A. L.

16

您可以使用注解@XmlType,并以不同的组合使用factoryMethod / factoryClass属性,例如:

@XmlType(factoryMethod="newInstance")
@XmlRootElement
public class PurchaseOrder {
    @XmlElement
    private final String address;
    @XmlElement
    private final Customer customer;

    public PurchaseOrder(String address, Customer customer){
        this.address = address;
        this.customer = customer;
    }

    private PurchaseOrder(){
        this.address = null;
        this.customer = null;
    }
    /** Creates a new instance, will only be used by Jaxb. */
    private static PurchaseOrder newInstance() {
        return new PurchaseOrder();
    }

    public String getAddress() {
        return address;
    }

    public Customer getCustomer() {
        return customer;
    }
}

令人惊讶的是,这个方法可以工作并在非编组时返回一个初始化实例。您应该记住不要在代码中调用newInstance方法,因为它会返回一个无效的实例。


3
创建公共方法会使其对任何人都可访问,而不仅仅是JAXB,这可能会破坏您的设计并使在设计时引入的字段约束无效。我不能推荐这个解决方案! - Eric Tobias
是的,我知道这一点,但通常情况下,这取决于你为了简单起见愿意让步什么。你可以定义所有方法的接口,从而隐藏任何其他实用方法,但我不确定原始帖子想要解决什么问题。 - Rafael M
3
你可以将修改器更改为“protected”或“private”。这样你的设计就不会被完全削弱。 - thomas.mc.work
1
可以确认,在newInstance()上使用private可以缓解@Eric的担忧。 - muttonUp
1
更新私人回复。 - Rafael M
显示剩余2条评论

5

为了让JAXB能够实例化您的类,您应该拥有一个默认构造函数。也许有一种我不知道的解决方法。

JAXB特别适用于类似bean的类,允许通过在对象上调用setter来配置对象。


1
为所有类创建默认构造函数是非常糟糕的做法。必须有一些解决方法。 - Stan Kurilin
我坦率地说,我不太理解无参种族主义。您的Bean类应该只是信息容器,可以轻松转换为XML,而不是具有复杂初始化的类。 - Valentin Rocher
1
这与对不可变数据对象的偏好有关。 - Guillaume
@Valentin Rocher,这里并没有非常复杂的初始化,但是有一些带有不可变状态的对象。 - Stan Kurilin
2
如果JAXB可以创建您的对象,则它不是不可变的。因为构造函数中可能存在其他操作,这可能会导致错误的对象,所以JAXB无法猜测要使用哪个构造函数。 - Valentin Rocher
查看我的答案,了解如何使用JAXB完成此操作:https://dev59.com/Dm855IYBdhLWcg3wXTAI#4387534 - bdoughan

3
JAXB以简单的方式从XML重新创建bean:它创建一个bean的新实例,然后执行所有必要的setXXX操作来设置属性。因此,如果您的bean没有无参构造函数,JAXB将无法创建它。正如其他答案中所说,JAXB更适用于简单的“容器”bean,对于这些bean,无参构造函数并不是真正的问题。如果您正在尝试创建需要特定初始化的bean,则需要在setXXX方法中进行初始化。

2
我认为在处理不可变对象等方面必须有一些实践。 - Stan Kurilin
@Stas:请看一下我对Guillaume答案的评论。如果JAXB必须创建它,那么它就不可能是不可变的。JAXB并不是用来创建不可变对象的,而更多地是用于创建“传输”对象,以便稍后从中获取信息。 - Valentin Rocher
更简单地说,如果它没有公共无参数构造函数,那么它就不是一个Bean。这是JavaBean规范的一部分。 - Joeri Hendrickx
这可以使用XML适配器完成,有关更多详细信息,请参阅我的更新答案:https://dev59.com/Dm855IYBdhLWcg3wXTAI#4387534 - bdoughan
我通常会使用工厂模式。为JAXB创建一个无参数构造函数。然后创建一个静态工厂方法,接受所有参数并处理调用setter。 - Jon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接