Rest API处理XML请求特殊字符报错问题及解决方案
这个问题我之前对接第三方XML接口时也碰到过,本质是XML语法的硬性规则在起作用,我给你拆解下原因和对应的解决办法:
为什么会报错?
XML语法里,&是预定义的特殊字符,它的作用是标记实体引用(比如&才是合法的&符号表示,还有<代表<、>代表>这些)。当XML解析器看到单独的&,但后面没有跟合法的实体名称时,就会直接抛出SAXParseException——这不是Java或者Spring的bug,是XML规范要求必须这么做。
你的请求负载里DESCRIPTION="Johnson & Johnsons"直接用了未转义的&,完全违反了XML语法,所以解析器直接拒绝处理了。
怎么解决?
分两种场景来处理,优先选最规范的那种:
场景1:能要求请求发送方修改
最稳妥的做法是让请求方按照XML规范转义特殊字符,把&替换成&就行,其他特殊字符如果出现也要对应转义。修正后的请求应该是这样:
<NAMES> <NAME ID="P2" DESCRIPTION="Johnson & Johnsons" /> </NAMES>
这样Spring默认的JAXB解析器就能正常解析,不需要改任何代码。
场景2:没法改请求方,必须在服务端兼容
如果请求方是第三方系统没法修改,那只能自定义XML解析逻辑,让它容忍未转义的&(注意:这是违反XML规范的操作,可能带来安全风险,一定要谨慎)。
给你两种实现方式:
方式1:用Jackson XML替代JAXB
Spring默认用JAXB解析XML,我们可以换成Jackson的XML解析器,它支持宽松配置:
- 先添加Jackson XML依赖(如果还没加):
<!-- Maven --> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> </dependency>
- 配置自定义的XML消息转换器:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import com.fasterxml.jackson.dataformat.xml.XmlMapper; import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator; import java.util.List; @Configuration public class XmlWebConfig implements WebMvcConfigurer { @Bean public MappingJackson2XmlHttpMessageConverter customXmlConverter() { XmlMapper xmlMapper = new XmlMapper(); // 允许未转义的特殊字符 xmlMapper.configure(ToXmlGenerator.Feature.ALLOW_UNQUOTED_CONTENT_CHARS, true); return new MappingJackson2XmlHttpMessageConverter(xmlMapper); } @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { // 移除默认的JAXB转换器,用自定义的Jackson XML转换器替代 converters.removeIf(converter -> converter instanceof org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter); converters.add(customXmlConverter()); } }
这样配置后,Spring就会用Jackson的XML解析器来处理请求,就能容忍未转义的&了。
方式2:修改JAXB的SAX解析器配置
如果你想继续用JAXB,可以给它配置一个宽松的SAX解析器:
// 可以封装成一个工具类,或者在Controller里手动解析(不推荐在Controller里写,最好封装成Converter) import javax.xml.bind.JAXBContext; import javax.xml.bind.Unmarshaller; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; public class XmlUnmarshallerUtil { public static <T> T unmarshal(Class<T> clazz, InputStream inputStream) throws Exception { SAXParserFactory spf = SAXParserFactory.newInstance(); // 关闭DTD验证和命名空间,开启宽松模式 spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); spf.setFeature("http://xml.org/sax/features/validation", false); // 关键配置:允许未转义的& spf.setFeature("http://apache.org/xml/features/allow-unquoted-attributes", true); SAXParser saxParser = spf.newSAXParser(); JAXBContext jc = JAXBContext.newInstance(clazz); Unmarshaller unmarshaller = jc.createUnmarshaller(); unmarshaller.setProperty("org.xml.sax.XMLReader", saxParser.getXMLReader()); return (T) unmarshaller.unmarshal(inputStream); } }
然后在Controller里手动调用这个工具类解析请求体,而不是用@RequestBody:
@PostMapping(value = "/UpdateSomeThings", consumes = { MediaType.APPLICATION_XML }) public ResponseEntity<SomeResponseModel> UpdateSomeThings( @PathVariable("mId") String mId, HttpServletRequest request) throws Exception { SomeMapModel reqBody = XmlUnmarshallerUtil.unmarshal(SomeMapModel.class, request.getInputStream()); // 后续逻辑... return new ResponseEntity<>(response, HttpStatus.OK); }
重要提醒
- 优先选择场景1的规范方案,违反XML规范的宽松解析可能带来XXE攻击风险,或者解析其他合法XML时出现奇怪的问题。
- 如果必须用宽松解析,一定要对输入做严格的校验和过滤,避免恶意XML注入。
内容的提问来源于stack exchange,提问作者USM




