JAXB - 对动态标签名称使用继承

JAXB - using inheritence for dynamic tag names

我正在尝试使用 JAXB 将 XML 组织成 class 层次结构 - 我希望层次结构以通用方式使用继承。我会更好地解释:

我有以下 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>

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

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

 @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 classes 以从 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;
        }
}

我已经尝试了这个基本代码的各种变体,但没有一个能奏效。我没有添加我所做的任何特定的,因为它们都抛出了非常严重的异常,这似乎表明我真的不在正确的道路上,所以我决定添加这个基本框架,并希望得到你们的任何指示...

谁能帮我解决这个问题? 谢谢

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

首先我使用以下 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>
    <Country name="France">
        <City name="Paris">
            <Street name="Champs-Elysees" />
            <Street name="La sante" />
        </City>
    </Country>
</root>

至于 classes 本身,我保留了与您相同的结构,Country、City 和 Street 继承自 GeneralLocation。但是,我从 GeneralLocation 中删除了 children 列表,取而代之的是每个 class(街道除外)都包含一个具有正确 children 类型(例如,国家内的城市)的列表。 它似乎最适合您的目标,因为您的原始结构允许 Street 将 City 或 Country 作为 children,如果您想从 class 模型编组到 [=],除了自然矛盾之外,这可能会导致问题52=]。因此,我以以下 classes 结尾:

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() {
   }

}

街道没有 children,因此无法将城市或国家保存为 children。我也刚刚意识到你没有在 class 定义之上添加 @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;
    }
}

City 和 Country 在这里真的很相似,它们都持有各自 children 的列表(用户不能搞砸 = 安全)并且 JAXB 可以访问 super[=48 中的 name 属性=] 通过注释(但这取决于您的访问策略,见下文)。最后我放了一个根 class,它被标记为 @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);
}

遗憾的是,我无法通过 GeneralLocation 持有的 children 使 JAXB 达到 运行,我得到了一个 UnmarshalException(无法创建 GeneralLocation 的实例),但我希望这有帮助。我认为这是解决问题的更合适的方法,虽然它不那么灵活但确保文档的结构是一致的。

您还应该查看 Blaise Doughan 博客:http://blog.bdoughan.com/,它包含许多对 JAXB 开发人员有用的信息;)

我想向您推荐一种不同的方法,因为我认为您使用了错误的工具来解决您的问题。当您使用 data projection(披露:我隶属于该项目)而不是数据绑定时,您会得到一个简短而通用的解决方案,恕我直言,JAXB 无法实现:

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 解决方案,但它更短而且有效。

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

Java 型号

一般位置

您可以在 sons 属性 上使用 @XmlElementRef 注释。 XML 中元素的名称将基于与所引用对象的子class 关联的根元素。

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;
    }

}

国家/地区

子class不需要覆盖sons 属性。他们唯一需要的是 @XmlRootElement 注释,GeneralLocation 上的 @XmlElementRef 注释将使用该注释来派生元素名称。

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。请注意,由于我们在 GeneralLocation class 上使用了 @XmlSeeAlso 注释,所以当我们 bootstrap [=] 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);
    }

}

输入.xml/Output

您问题中的 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>