JAXB - 将继承用于动态标记名称

时间:2015-01-08 14:47:56

标签: java xml inheritance jaxb

我试图使用JAXB将XML编组到类heiarchy中 - 我希望heiarchy以通用的方式使用继承。我会更好地解释一下:

我有以下XML:

<Country name="USA">
    <City name="NewYork">
      <Street name="Something"/>
      <Street name="Something2"/>
    </City>
    <City name="LosAngeles">
       <Street name="Something"/>
       <Street name="Something2"/>
    </City>
<Country>
<Country .....>
   <.....>
    <.....
</Country>

等。其中eatch国家可以有多个城市,每个城市可以有多条街道。

我想创建一个名为GeneralLocation的类,它看起来像这样:

 @XmlTransient
    public abstract class GeneralLocation {

        private String name;
        protected List<GeneralLocation> sons;


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

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

        public List<GeneralLocation> getSons(){
              return this.sons
        }

        public void setSons(List<GeneralLocation> sons){
              this.sons = sons;
        }
    }

然后创建Country,City和Street类以从GeneralLocation继承,并使用正确的JAXB解析名称覆盖getSons方法。

public class Country extends GeneralLocation{

        @XmlElement(name="City")
        public List<GeneralLocation> getSons(){
              return this.sons
        }

        public void setSons(List<GeneralLocation> sons){
              this.sons = sons;
        }
}

我已经尝试过这个基本代码的各种变体,但没有一个能够完成这项工作。我没有添加任何特定的一个,因为他们都抛出了似乎表明我真的不在正确的道路上的奇怪的例外,所以我决定添加这个基本的骨架并希望你们的任何指针......

任何人都可以帮我吗? 感谢

3 个答案:

答案 0 :(得分:5)

您可以使用@XmlElementRef注释作为用例。

Java模型

<强> GeneralLocation

您可以在@XmlElementRef媒体资源上使用sons注释。 XML中元素的名称将基于与引用的对象的子类关联的根元素。

import java.util.List;
import javax.xml.bind.annotation.*;

@XmlSeeAlso({Country.class, City.class, Street.class})
public abstract class GeneralLocation {

    private String name;
    protected List<GeneralLocation> sons;

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

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

    @XmlElementRef
    public List<GeneralLocation> getSons(){
        return this.sons;
    }

    public void setSons(List<GeneralLocation> sons){
        this.sons = sons;
    }

}

<强>国家

子类不需要覆盖sons属性。他们唯一需要的是@XmlRootElement注释,@XmlElementRef注释将GeneralLocation用于导出元素名称。

import javax.xml.bind.annotation.*;

@XmlRootElement(name="Country")
public class Country extends GeneralLocation {
}

<强>城市

import javax.xml.bind.annotation.*;

@XmlRootElement(name="City")
public class City extends GeneralLocation {
}

街景

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name="Street")
public class Street extends GeneralLocation {
}

演示代码

下面是一些示例代码,它将XML解组为对象,然后再将其封送回XML。请注意,由于我们在@XmlSeeAlso类上使用了GeneralLocation注释,因此在引导JAXBContext时我们不需要指定所有子类。

<强>演示

import java.io.File;
import javax.xml.bind.*;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(GeneralLocation.class);

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        File xml = new File("input.xml");
        GeneralLocation result = (GeneralLocation) unmarshaller.unmarshal(xml);

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(result, System.out);
    }

}

<强> input.xml中/输出

您问题中的XML无效,因为您没有一个根元素,所以对于我的回答,我选择只使用一个国家。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Country name="USA">
    <City name="NewYork">
        <Street name="Something"/>
        <Street name="Something2"/>
    </City>
    <City name="LosAngeles">
        <Street name="Something"/>
        <Street name="Something2"/>
    </City>
</Country>

答案 1 :(得分:1)

我的解决方案并不像您希望的那样通用,但我认为它可能有助于您理解为什么您收到了各种异常(我自己偶然发现许多人试图找到解决方案)。

首先,我使用以下XML作为测试(我假设你省略了root元素):

<root>
    <Country name="USA">
        <City name="NewYork">
            <Street name="Something" />
            <Street name="Something2" />
        </City>
        <City name="LosAngeles">
            <Street name="Something" />
            <Street name="Something2" />
        </City>
    </Country>
    <Country name="France">
        <City name="Paris">
            <Street name="Champs-Elysees" />
            <Street name="La sante" />
        </City>
    </Country>
</root>

至于课程本身,我保持与你的相同的结构,Country,City和Street继承自GeneralLocation。但是,我从GeneralLocation中删除了子项列表,而每个类(Street除外)都拥有一个具有正确子类型的List(例如,Country in Country)。 它似乎最符合您的目标,因为您的原始结构允许Street将City或Country作为子项,除了自然矛盾之外,如果您想从类模型编组为XML,则可能会导致问题。因此,我结束了以下课程:

public abstract class GeneralLocation {

    protected String name;

    public GeneralLocation() {
        name = "";
    }

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

我摆脱了@XmlTransient注释,因为根据我的经验,这不是必需的。

@XmlType
public class Street extends GeneralLocation {

    public Street() {
   }

}

街道没有孩子,因此无法将城市或国家作为儿童。我也刚刚意识到你没有在类定义之上的@XmlType注释,这可能是解析错误的原因。

@XmlType
public class City extends GeneralLocation {

    private List<Street> streets;

    public City() {
        streets = new LinkedList<Street>();
    }

    @XmlElement(name = "Street")
    public List<Street> getStreets() {
        return streets;
    }

}

@XmlType
public class Country extends GeneralLocation{

    private List<City> cities;

    public Country() {
        cities = new LinkedList<City>();
    }

    @XmlElement(name="City")
    public List<City> getCities() {
        return cities;
    }
}

@XmlRootElement(namespace = "", name = "root")
public class Root {

    private List<Country> countries;

    public Root() {
        countries = new LinkedList<Country>();
    }

    @XmlElement(name = "Country")
    public List<Country> getCountries() {
        return countries;
    }
}

城市和国家在这里非常相似,他们都拥有各自孩子的列表(用户不能搞砸=安全性),JAXB可以通过注释访问超类中的name属性(但这取决于你的访问政策,见下文)。最后,我放了一个用@XmlRootElement标记的根类,并保存了这些国家。

最后,我建议您在包定义中定义访问者类型(文件package-info.java),以确保JAXB访问数据的方式是一致的(例如,只有注释):

@XmlAccessorType(XmlAccessType.NONE) //annotations only
package test;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAccessType;

有了这个你应该全部解开你的xml :)作为奖励,我使用的主要:

public static void main(String[] args) throws JAXBException {

    JAXBContext jaxbc = JAXBContext.newInstance(Root.class);
    Unmarshaller unm = jaxbc.createUnmarshaller();
    File countries = new File("countries.xml");
    Root result = (Root) unm.unmarshal(countries);
}

遗憾的是,我无法让JAXB与GeneralLocation持有的子项一起运行,我得到一个UnmarshalException(无法创建GeneralLocation的实例),但我希望无论如何这都有帮助。我认为这是解决问题的更合适的方法,但它并不灵活,但要确保文档的结构是一致的。

另外你应该看看Blaise Doughan的博客:http://blog.bdoughan.com/,它包含了很多有用的JAXB开发人员信息;)

答案 2 :(得分:0)

我想建议你采用不同的方法,因为我认为你使用了错误的工具来解决问题。当您使用data projection(披露:我与此项目相关联)而不是数据绑定时,您会得到一个简短且通用的解决方案,即JHOB无法实现的恕我直言:

public class LocationExample {

    public interface GeneralLocation {
        @XBRead("@name")
        String getName();

        @XBRead("name()")
        String getType();

        @XBRead("./*")
        List<GeneralLocation> getSons();

        @XBRead("/root/Country")
        List<GeneralLocation> getCountries();
    }

    public static void main(String... args) throws IOException {
         GeneralLocation location = new XBProjector().io().url("resource://locations.xml").read(GeneralLocation.class);
         printLocations(location.getCountries());
    }

    public static void printLocations(List<GeneralLocation> locations) {       
        for (GeneralLocation son:locations) {
            System.out.println(son.getType()+": "+son.getName());
            printLocations(son.getSons());
        }
    }
}

我需要在xml中添加一个根元素才能使其有效。输入

<root>
    <Country name="USA">
        <City name="NewYork">
            <Street name="Something" />
            <Street name="Something2" />
        </City>
        <City name="LosAngeles">
            <Street name="Something" />
            <Street name="Something2" />
        </City>
    </Country>
</root>

程序打印出来:

Country: USA
City: NewYork
Street: Something
Street: Something2
City: LosAngeles
Street: Something
Street: Something2

我知道这不是您要求的JAXB解决方案,但它更短并且有效。