You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过继承实现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,实现如下:

  1. 定义INFO类:
@XmlRootElement(name="INFO")
public class INFO {
    @XmlElement
    public String ret_code;
    @XmlElement 
    public String details;
}
  1. 定义ADDRESS类:
@XmlRootElement(name = "address")
public class ADDRESS{
    @XmlElement
    public String city;
    @XmlElement
    public String street;
}
  1. 姓名-地址接口的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;
}
  1. 姓名-地址接口的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;
}
  1. 薪资接口的Response类(继承方式):
@XmlRootElement(name="RESPONSE")
public class Response extends INFO{

    @XmlElement(name="salary")
    public String salary;
}

遇到的问题:

  • 继承方式下抛出ClassCastException,无法将INFO转换为Response;
  • 非继承方式下,INFO info和addresses字段值为null。

解决方案

核心问题在于:

  1. 错误地将RESPONSE与INFO设计为继承关系(实际是包含关系);
  2. 未正确映射XML中的<CONTENT>标签,导致内部字段无法被解析;
  3. 未通过泛型实现公共响应结构的复用。

以下是优化后的实现:

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 20:05:10