如何用JAXB在SOAP响应中添加XSD未定义的XML属性?
我的服务需要调用下游服务,其SOAP响应包含大量属性,示例如下:
<section name="AA1"> <grp name="..." AB1001="1.5" AB1002="..." .../> <grp name="..." CD1001="1.5" CD1F002="..." .../> ... </section> <section name="AB1"> <grp name="..." AB1001="1.5" AB1002="..." .../> <grp name="..." CD1001="1.5" CD1F002="..." .../> ... </section> <section name="AB2"> ... </section> ...
我的服务响应需要保留该结构,但由于属性数量庞大,我不想将所有属性添加到自身XSD中,而是希望读取下游服务响应中的属性并将其添加到自身SOAP响应中。
目前我使用JAXB,Java XML Bean由XSD生成,而这些XML属性未在自身XSD中定义,因此需要在JAXB Bean编组为XML(SOAP响应)后手动添加这些属性。
请问是否有可行的实现方式?
我使用Spring和Spring Integration,出站网关配置如下:
@Configuration public class MppWebServiceGatewayConfig { ... @Bean(name="serviceOutboundGateway") public MessageHandler serviceOutboundGateway() { final MarshallingWebServiceOutboundGateway gateway = new ExceptionHandlingMarshallingWebServiceOutboundGateway(url, mppMarshaller(), mppUnMarshaller()); gateway.setMessageSender(httpPoolingWebServiceMessageSender); return gateway; } @Bean public Marshaller mppMarshaller() { final Jaxb2Marshaller marshaller = new Jaxb2Marshaller(); marshaller.setContextPath(CONTEXTPATH); return marshaller; } @Bean public Unmarshaller mppUnMarshaller() { final Jaxb2Marshaller unmarshaller = new Jaxb2Marshaller(); unmarshaller.setContextPath(CONTEXTPATH); return unmarshaller; } ... }
方法一:利用JAXB的@XmlAnyAttribute捕获未知属性
这是最直接的JAXB原生解决方案,无需在编组后手动修改XML。
修改XSD或生成的Bean
如果是直接修改生成的Bean,在Section和Grp类中添加注解字段存储未知属性:public class Section { @XmlAttribute private String name; @XmlElement(name = "grp") private List<Grp> grps; @XmlAnyAttribute private Map<QName, String> otherAttributes; public Map<QName, String> getOtherAttributes() { if (otherAttributes == null) { otherAttributes = new HashMap<>(); } return otherAttributes; } } public class Grp { @XmlAttribute private String name; @XmlAnyAttribute private Map<QName, String> otherAttributes; public Map<QName, String> getOtherAttributes() { if (otherAttributes == null) { otherAttributes = new HashMap<>(); } return otherAttributes; } }若通过XSD生成Bean,直接在XSD中添加
xs:anyAttribute配置:<xs:complexType name="SectionType"> <xs:sequence> <xs:element name="grp" type="GrpType" maxOccurs="unbounded"/> </xs:sequence> <xs:attribute name="name" type="xs:string"/> <xs:anyAttribute processContents="lax"/> </xs:complexType> <xs:complexType name="GrpType"> <xs:attribute name="name" type="xs:string"/> <xs:anyAttribute processContents="lax"/> </xs:complexType>重新生成Bean后,JAXB会自动将下游响应中的未知属性填充到
otherAttributes集合,编组时直接输出这些属性,无需额外处理。适配Spring配置
现有Jaxb2Marshaller配置无需修改,它会自动识别@XmlAnyAttribute注解,完成反序列化捕获与序列化输出。
方法二:在编组后通过DOM操作修改XML
若不想修改XSD或生成的Bean,可在编组完成后通过DOM API手动注入属性。
自定义Marshaller装饰器
创建包装类,在编组后处理XML文档:public class CustomMarshaller implements Marshaller { private final Marshaller delegate; public CustomMarshaller(Marshaller delegate) { this.delegate = delegate; } @Override public void marshal(Object graph, Result result) throws JAXBException { if (result instanceof DOMResult) { delegate.marshal(graph, result); Node node = ((DOMResult) result).getNode(); injectDownstreamAttributes(node); } else { delegate.marshal(graph, result); } } private void injectDownstreamAttributes(Node node) { if (node.getNodeType() == Node.ELEMENT_NODE) { Element element = (Element) node; if ("section".equals(element.getNodeName())) { Map<QName, String> sectionAttrs = getSectionAttributes(element.getAttribute("name")); sectionAttrs.forEach((qName, value) -> element.setAttributeNS(qName.getNamespaceURI(), qName.getLocalPart(), value)); } else if ("grp".equals(element.getNodeName())) { Map<QName, String> grpAttrs = getGrpAttributes(element.getAttribute("name")); grpAttrs.forEach((qName, value) -> element.setAttributeNS(qName.getNamespaceURI(), qName.getLocalPart(), value)); } NodeList children = element.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { injectDownstreamAttributes(children.item(i)); } } } // 替换为实际从下游响应获取属性的逻辑 private Map<QName, String> getSectionAttributes(String sectionName) { Map<QName, String> attrs = new HashMap<>(); attrs.put(new QName("", "customSectionAttr"), "value"); return attrs; } private Map<QName, String> getGrpAttributes(String grpName) { Map<QName, String> attrs = new HashMap<>(); attrs.put(new QName("", "AB1001"), "1.5"); attrs.put(new QName("", "CD1001"), "1.5"); return attrs; } // 其余Marshaller接口方法全部委托给delegate实现 @Override public void marshal(Object graph, Result result, Marshaller.Listener listener) throws JAXBException { delegate.marshal(graph, result, listener); } @Override public void setProperty(String name, Object value) throws PropertyException { delegate.setProperty(name, value); } @Override public Object getProperty(String name) throws PropertyException { return delegate.getProperty(name); } @Override public void setListener(Marshaller.Listener listener) { delegate.setListener(listener); } @Override public Marshaller.Listener getListener() { return delegate.getListener(); } @Override public void setAdapter(Adapter adapter) { delegate.setAdapter(adapter); } @Override public <A extends Adapter> void setAdapter(Class<A> type, A adapter) { delegate.setAdapter(type, adapter); } @Override public <A extends Adapter> A getAdapter(Class<A> type) { return delegate.getAdapter(type); } }修改Spring配置
在Marshaller Bean中包装原始Jaxb2Marshaller:@Bean public Marshaller mppMarshaller() { final Jaxb2Marshaller marshaller = new Jaxb2Marshaller(); marshaller.setContextPath(CONTEXTPATH); return new CustomMarshaller(marshaller); }
方法三:直接转发下游XML片段(适用于无需修改结构的场景)
若服务响应仅需原样转发下游的section结构,可跳过JAXB序列化/反序列化,直接处理XML payload:
修改出站网关配置
使用WebServiceOutboundGateway直接处理XML:@Bean(name="serviceOutboundGateway") public MessageHandler serviceOutboundGateway() { WebServiceOutboundGateway gateway = new WebServiceOutboundGateway(url); gateway.setMessageSender(httpPoolingWebServiceMessageSender); gateway.setRequestMessageConverter(new SimpleXmlPayloadConverter()); gateway.setResponseMessageConverter(new SimpleXmlPayloadConverter()); return gateway; }消息流中拼接XML
接收到下游XML响应后,通过XSLT或DOM操作提取section节点,直接拼接到服务响应XML中。
内容的提问来源于stack exchange,提问作者Viktor

