我有一个已经被其他应用程序使用的固定XML结构。其中一些是第三方,因此更改XML不是一种选择。
XML包含一个我正在努力解组的部分。下面是一个减少版本。此元素是其他元素的子元素。
<premium>
<allowInstalments>true</allowInstalments>
<annualPremium>2964.23</annualPremium>
<!-- other various elements -->
<calcElement partname="driver">
<driverXs>300.00</driverXs>
<seq>1</seq>
</calcElement>
<calcElement partname="ratingData">
<baseMiles>6000</baseMiles>
<vehicleGroup>15</vehicleGroup>
<documentVersion>4</documentVersion>
</calcElement>
</premium>
为了测试这个解组正确(和编组,但我现在正试图解组),我写了以下测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RegressionApplication.class})
public class AdaptedCalcElementTest {
@Autowired
@Qualifier(value = "unmarshaller")
private Unmarshaller unmarshaller;
@Test
public void canUnmarshallIntoDriverCalcElement() throws Exception {
String xml = "<wrapper><calcElement partname=\"driver\">" +
"<driverXs>300.00</driverXs>" +
"<seq>1</seq>" +
"</calcElement></wrapper>";
CalcElementWrapper calcElementWrapper = (CalcElementWrapper) unmarshaller.unmarshal(Input.from(xml).build());
assertThat(calcElementWrapper, notNullValue());
assertThat(calcElementWrapper.listElements, notNullValue());
assertThat(calcElementWrapper.listElements, hasSize(1));
CalcElement calcElement = calcElementWrapper.listElements.get(0);
assertThat(calcElement, instanceOf(DriverCalcElement.class));
}
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "wrapper")
public static class CalcElementWrapper {
@XmlJavaTypeAdapter(CalcElementAdapter.class)
public List<CalcElement> listElements;
}
}
我的Adapter类根据pathname属性创建正确的CalcElement扩展类:
public class CalcElementAdapter extends XmlAdapter<CalcElementAdapter.AdaptedCalcElement, CalcElement> {
@Override
public CalcElement unmarshal(CalcElementAdapter.AdaptedCalcElement v) throws Exception {
if (v.partname.equalsIgnoreCase("driver")) {
DriverCalcElement calcElement = new DriverCalcElement();
calcElement.setPartname(v.partname);
calcElement.setDriverXs(new BigDecimal(v.driverXs));
calcElement.setSeq(new Integer(v.seq));
return calcElement;
} else if (v.partname.equalsIgnoreCase("ratingData")) {
RatingDataCalcElement calcElement = new RatingDataCalcElement();
calcElement.setBaseMiles(new Integer(v.baseMiles));
calcElement.setDocumentVersion(new Integer(v.documentVersion));
calcElement.setVehicleGroup(new Integer(v.vehicleGroup));
return calcElement;
}
return null;
}
@Override
public CalcElementAdapter.AdaptedCalcElement marshal(CalcElement v) throws Exception {
return null;
}
public static class AdaptedCalcElement {
@XmlAttribute
public String partname;
public String driverXs;
public String seq;
public String baseMiles;
public String vehicleGroup;
public String documentVersion;
}
}
CalcElement和派生类定义如下:
public abstract class CalcElement {
private String partname;
@XmlAttribute
public String getPartname() {
return partname;
}
public void setPartname(String partname) {
this.partname = partname;
}
}
public class DriverCalcElement extends CalcElement {
private BigDecimal driverXs;
private Integer seq;
public BigDecimal getDriverXs() {
return driverXs;
}
public void setDriverXs(BigDecimal driverXs) {
this.driverXs = driverXs;
}
public Integer getSeq() {
return seq;
}
public void setSeq(Integer seq) {
this.seq = seq;
}
}
public class RatingDataCalcElement extends CalcElement {
private Integer baseMiles;
private Integer vehicleGroup;
private Integer documentVersion;
public Integer getBaseMiles() {
return baseMiles;
}
public void setBaseMiles(Integer baseMiles) {
this.baseMiles = baseMiles;
}
public Integer getVehicleGroup() {
return vehicleGroup;
}
public void setVehicleGroup(Integer vehicleGroup) {
this.vehicleGroup = vehicleGroup;
}
public Integer getDocumentVersion() {
return documentVersion;
}
public void setDocumentVersion(Integer documentVersion) {
this.documentVersion = documentVersion;
}
}
unmarshaller配置如下:
@Bean(name = "unmarshaller")
Unmarshaller getUnmarshaller() {
return getJaxb2Marshaller();
}
private Jaxb2Marshaller getJaxb2Marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setPackagesToScan("com.itb.lego.regression");
return marshaller;
}
我试图关注@ blaise-doughan的博客文章,但我看不出我错过了什么。
但是,测试失败,因为它没有为calcElement元素调用适配器。我错过了什么?
答案 0 :(得分:0)
我发现除了告诉JaxB如何解组CalcElement之外我还要走得更远。
我的第一步是创建一个可以从任何类型的CalcElement解组的类。
@XmlRootElement(name = "calcElement")
public class AdaptableCalcElement {
@XmlAttribute
public String partname;
@XmlElement
public String driverXs;
@XmlElement
public String seq;
@XmlElement
public String baseMiles;
@XmlElement
public String vehicleGroup;
@XmlElement
public String documentVersion;
}
所有元素都是字符串,因为这只是一个过渡类,但它将保存所有可用数据,以允许我编写逻辑来创建特定的CalcElement派生类。
因为calcElement元素没有包含在包含元素中,并且两个包含类之间的差异是直接子元素。这意味着我需要2个不同的包装类。一个用于保存AdaptableCalcElement列表,然后保存最终版本以保存CalcElement列表。
这给了我以下两个课程:
@XmlAccessorType(XmlAccessType.PROPERTY)
@XmlRootElement(name = "premium")
public class AdaptablePremium extends BasePremium {
private List<AdaptableCalcElement> calcElements;
@XmlElement(name = "calcElement")
public List<AdaptableCalcElement> getCalcElements() {
return calcElements;
}
public void setCalcElements(List<AdaptableCalcElement> calcElements) {
this.calcElements = calcElements;
}
}
和
public class Premium extends BasePremium {
List<CalcElement> calcElements;
public List<CalcElement> getCalcElements() {
return calcElements;
}
public void setCalcElements(List<CalcElement> calcElements) {
this.calcElements = calcElements;
}
}
这两个都是从BasePremium扩展而来的,它包含了两者共有的所有字段。
此时我可以将XML解组为AdaptablePremium。高级XML仅在包含XML的情况下有效,这允许我们指定容器(Details)包含Premium,以及如何解组它。
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "detail")
public class Detail {
// All other fields for this class
@XmlJavaTypeAdapter(PremiumAdapter.class)
private Premium premium;
}
现在这意味着当我解组详细信息元素时,它将使用此类并尝试使用PremiumAdapter类解组高级元素。 PremiumAdapter类定义了如何将AdaptablePremium转换为Premium。 BasePremium有很多成员(非常糟糕的XML结构,但它是固定的),因此我不是复制每个成员,而是使用反射来复制除CalcElements之外的所有内容,然后明确地执行此操作。我留下了以下XmlAdapter类
@Component
public class PremiumAdapter extends XmlAdapter<AdaptablePremium, Premium> {
@Override
public Premium unmarshal(AdaptablePremium adaptablePremium) throws Exception {
Premium premium = new Premium();
Class<?> premiumClass = premium.getClass();
Class<?> adaptablePremiumClass = adaptablePremium.getClass();
ReflectionUtils.doWithFields(Premium.class,
field -> {
String fieldName = capitalise(field.getName());
Class<?> type = field.getType();
try {
Method getter = adaptablePremiumClass.getMethod("get" + fieldName);
Method setter = premiumClass.getMethod("set" + fieldName, type);
String value = (String) ReflectionUtils.invokeMethod(getter, adaptablePremium);
if (type.equals(String.class)) {
ReflectionUtils.invokeMethod(setter, premium, value);
} else if (type.equals(BigDecimal.class)) {
ReflectionUtils.invokeMethod(setter, premium, new BigDecimal(value));
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
},
field -> !field.getName().equalsIgnoreCase("calcElements")
);
adaptCalcElements(adaptablePremium, premium);
return premium;
}
public String capitalise(String name) {
return name.substring(0, 1).toUpperCase() + name.substring(1);
}
public void adaptCalcElements(AdaptablePremium adaptablePremium, Premium premium) {
List<CalcElement> calcElements = new ArrayList<>();
for(AdaptableCalcElement adaptableCalcElement : adaptablePremium.getCalcElements()) {
if (adaptableCalcElement.partname.equalsIgnoreCase("driver")) {
DriverCalcElement calcElement = new DriverCalcElement();
calcElement.setPartname(adaptableCalcElement.partname);
calcElement.setDriverXs(new BigDecimal(adaptableCalcElement.driverXs));
calcElement.setSeq(new Integer(adaptableCalcElement.seq));
calcElements.add(calcElement);
} else if (adaptableCalcElement.partname.equalsIgnoreCase("ratingData")) {
RatingDataCalcElement calcElement = new RatingDataCalcElement();
calcElement.setPartname(adaptableCalcElement.partname);
calcElement.setBaseMiles(new Integer(adaptableCalcElement.baseMiles));
calcElement.setDocumentVersion(new Integer(adaptableCalcElement.documentVersion));
calcElement.setVehicleGroup(new Integer(adaptableCalcElement.vehicleGroup));
calcElements.add(calcElement);
}
}
premium.setCalcElements(calcElements);
}
@Override
public AdaptablePremium marshal(Premium v) throws Exception {
// Currently unimplemented
return null;
}
}
我还有一些重构要做,但现在可以正确地将xml解组为正确的类型。