如何通过继承实现JAXB XML转POJO?映射问题求助
问题背景
我从多个接口获取XML响应(无XSD文件),所有响应的基础结构相似:
- 姓名-地址接口响应:
<RESPONSE> <INFO> <ret_code>0</ret_code> <details></details> </INFO> <CONTENT> <name>John</name> <addresses> <address> <city>NewYork</city> <street>5th</street> </address> <address> <city>Paris</city> <street>Colombe</street> </address> </addresses> </CONTENT> </RESPONSE>
- 薪资接口响应(两种情况):
无数据的错误响应:
<RESPONSE> <INFO> <ret_code>1</ret_code> <details>no salary data</details> </INFO> </RESPONSE>
含数据的有效响应:
<RESPONSE> <INFO> <ret_code>0</ret_code> <details></details> </INFO> <CONTENT> <name>John</name> <salary>1000</salary> </CONTENT> </RESPONSE>
公共结构:所有响应都包含<RESPONSE>和<INFO>标签,<CONTENT>标签可选,且内部内容随接口不同而变化。
尝试的实现与问题
我尝试用JAXB将XML映射为POJO,实现如下:
- 定义
INFO类:
@XmlRootElement(name="INFO") public class INFO { @XmlElement public String ret_code; @XmlElement public String details; }
- 定义
ADDRESS类:
@XmlRootElement(name = "address") public class ADDRESS{ @XmlElement public String city; @XmlElement public String street; }
- 姓名-地址接口的
Response类(继承方式):
@XmlRootElement(name="RESPONSE") public class Response extends INFO{ @XmlElement(name="name") public String name; @XmlWrapperElement(name="addresses") @XmlElement(name="address") public List<Address> addresses; }
- 姓名-地址接口的
Response类(非继承方式):
@XmlRootElement(name="RESPONSE") public class Response { @XmlElement(name="INFO") public INFO info; @XmlElement(name="name") public String name; @XmlWrapperElement(name="addresses") @XmlElement(name="address") public List<Address> addresses; }
- 薪资接口的
Response类(继承方式):
@XmlRootElement(name="RESPONSE") public class Response extends INFO{ @XmlElement(name="salary") public String salary; }
遇到的问题:
- 继承方式下抛出
ClassCastException,无法将INFO转换为Response; - 非继承方式下,
INFO info和addresses字段值为null。
解决方案
核心问题在于:
- 错误地将
RESPONSE与INFO设计为继承关系(实际是包含关系); - 未正确映射XML中的
<CONTENT>标签,导致内部字段无法被解析; - 未通过泛型实现公共响应结构的复用。
以下是优化后的实现:
1. 定义公共的基础响应类
通过泛型复用RESPONSE和INFO的公共结构,同时支持不同的CONTENT类型:
import javax.xml.bind.annotation.*; @XmlRootElement(name = "RESPONSE") @XmlAccessorType(XmlAccessType.FIELD) public class BaseResponse<T> { @XmlElement(name = "INFO") private Info info; @XmlElement(name = "CONTENT") private T content; // Getters and Setters public Info getInfo() { return info; } public void setInfo(Info info) { this.info = info; } public T getContent() { return content; } public void setContent(T content) { this.content = content; } }
Info类(修正命名符合Java规范):
import javax.xml.bind.annotation.*; @XmlAccessorType(XmlAccessType.FIELD) public class Info { @XmlElement(name = "ret_code") private String retCode; private String details; // Getters and Setters public String getRetCode() { return retCode; } public void setRetCode(String retCode) { this.retCode = retCode; } public String getDetails() { return details; } public void setDetails(String details) { this.details = details; } }
2. 定义各接口的CONTENT数据类
针对不同接口的CONTENT内容,定义对应的实体类:
姓名-地址接口的Content类
import javax.xml.bind.annotation.*; import java.util.List; @XmlAccessorType(XmlAccessType.FIELD) public class AddressContent { private String name; @XmlElementWrapper(name = "addresses") @XmlElement(name = "address") private List<Address> addresses; // Getters and Setters public String getName() { return name; } public void setName(String name) { this.name = name; } public List<Address> getAddresses() { return addresses; } public void setAddresses(List<Address> addresses) { this.addresses = addresses; } }
Address类(修正命名):
import javax.xml.bind.annotation.*; @XmlAccessorType(XmlAccessType.FIELD) public class Address { private String city; private String street; // Getters and Setters public String getCity() { return city; } public void setCity(String city) { this.city = city; } public String getStreet() { return street; } public void setStreet(String street) { this.street = street; } }
薪资接口的Content类
import javax.xml.bind.annotation.*; @XmlAccessorType(XmlAccessType.FIELD) public class SalaryContent { private String name; private String salary; // Getters and Setters public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSalary() { return salary; } public void setSalary(String salary) { this.salary = salary; } }
3. JAXB解析示例
使用泛型解析不同接口的响应:
解析姓名-地址响应
import javax.xml.bind.JAXBContext; import javax.xml.bind.Unmarshaller; import java.io.StringReader; public class JaxbParser { public static void main(String[] args) throws Exception { String addressXml = "<RESPONSE>\n" + "<INFO>\n" + " <ret_code>0</ret_code>\n" + " <details></details>\n" + "</INFO>\n" + "<CONTENT>\n" + " <name>John</name>\n" + " <addresses>\n" + " <address>\n" + " <city>NewYork</city>\n" + " <street>5th</street>\n" + " </address>\n" + " <address>\n" + " <city>Paris</city>\n" + " <street>Colombe</street>\n" + " </address>\n" + " </addresses>\n" + "</CONTENT>\n" + "</RESPONSE>"; JAXBContext context = JAXBContext.newInstance(BaseResponse.class, AddressContent.class, Address.class, Info.class); Unmarshaller unmarshaller = context.createUnmarshaller(); BaseResponse<AddressContent> response = (BaseResponse<AddressContent>) unmarshaller.unmarshal(new StringReader(addressXml)); System.out.println("Ret code: " + response.getInfo().getRetCode()); System.out.println("Name: " + response.getContent().getName()); response.getContent().getAddresses().forEach(addr -> System.out.println("City: " + addr.getCity() + ", Street: " + addr.getStreet()) ); } }
解析薪资响应(含数据)
String salaryXml = "<RESPONSE>\n" + "<INFO>\n" + " <ret_code>0</ret_code>\n" + " <details></details>\n" + "</INFO>\n" + "<CONTENT>\n" + " <name>John</name>\n" + " <salary>1000</salary>\n" + "</CONTENT>\n" + "</RESPONSE>"; JAXBContext salaryContext = JAXBContext.newInstance(BaseResponse.class, SalaryContent.class, Info.class); Unmarshaller salaryUnmarshaller = salaryContext.createUnmarshaller(); BaseResponse<SalaryContent> salaryResponse = (BaseResponse<SalaryContent>) salaryUnmarshaller.unmarshal(new StringReader(salaryXml)); System.out.println("Ret code: " + salaryResponse.getInfo().getRetCode()); System.out.println("Name: " + salaryResponse.getContent().getName()); System.out.println("Salary: " + salaryResponse.getContent().getSalary());
解析无CONTENT的错误响应
String errorXml = "<RESPONSE>\n" + "<INFO>\n" + " <ret_code>1</ret_code>\n" + " <details>no salary data</details>\n" + "</INFO>\n" + "</RESPONSE>"; BaseResponse<SalaryContent> errorResponse = (BaseResponse<SalaryContent>) salaryUnmarshaller.unmarshal(new StringReader(errorXml)); System.out.println("Ret code: " + errorResponse.getInfo().getRetCode()); System.out.println("Details: " + errorResponse.getInfo().getDetails()); // content字段为null,需判断后使用
关键优化点说明
- 组合而非继承:
BaseResponse通过包含Info类实现结构复用,符合XML中RESPONSE包含INFO的实际关系,避免继承带来的类型转换问题; - 泛型支持多CONTENT类型:通过
BaseResponse<T>适配不同接口的CONTENT内容,无需重复定义RESPONSE结构; - 正确映射CONTENT标签:将接口特有的字段封装到
Content类中,并通过@XmlElement(name = "CONTENT")映射到XML的对应节点,解决之前字段为null的问题; - 使用
@XmlAccessorType简化配置:指定字段访问模式,无需为每个字段添加@XmlElement(除非需要重命名),简化代码。
内容的提问来源于stack exchange,提问作者Crypto Vice
相关产品推荐
相关产品推荐

