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>
我正在尝试使用 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>