使用 RestController 接受字符串和 XML 数据
Posted
技术标签:
【中文标题】使用 RestController 接受字符串和 XML 数据【英文标题】:Accept Strings and XML data with RestController 【发布时间】:2019-02-06 15:38:39 【问题描述】:我想创建接受 XML 请求和纯文本到不同控制器的 REST 服务器。我试图实现这一点:
@SpringBootApplication
public class Application extends SpringBootServletInitializer implements WebMvcConfigurer
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application)
return application.sources(Application.class);
..............
private BasicAuthenticationInterceptor basicAuthenticationInterceptor;
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters)
converters.removeIf(converter -> converter instanceof MappingJackson2XmlHttpMessageConverter);
converters.removeIf(converter -> converter instanceof MappingJackson2HttpMessageConverter);
converters.add(new MappingJackson2XmlHttpMessageConverter(
((XmlMapper) createObjectMapper(Jackson2ObjectMapperBuilder.xml()))
.enable(ToXmlGenerator.Feature.WRITE_XML_DECLARATION)));
converters.add(new MappingJackson2HttpMessageConverter(createObjectMapper(Jackson2ObjectMapperBuilder.json())));
private ObjectMapper createObjectMapper(Jackson2ObjectMapperBuilder builder)
builder.indentOutput(true);
builder.modules(new JaxbAnnotationModule());
builder.serializationInclusion(JsonInclude.Include.NON_NULL);
builder.defaultUseWrapper(false);
return builder.build();
@Autowired
public void setBasicAuthenticationInterceptor(BasicAuthenticationInterceptor basicAuthenticationInterceptor)
this.basicAuthenticationInterceptor = basicAuthenticationInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry)
registry.addInterceptor(basicAuthenticationInterceptor);
检查 XML 格式是否正确:
@ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler
@Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex,
HttpHeaders headers, HttpStatus status, WebRequest request)
PaymentTransaction response;
if (ex.getMessage().contains("Required request body"))
response = new PaymentTransaction(PaymentTransaction.Response.failed_response, 350,
"Invalid XML message: No XML data received", "XML request parsing failed!");
else
response = new PaymentTransaction(PaymentTransaction.Response.failed_response, 351,
"Invalid XML message format", null);
return ResponseEntity.badRequest().body(response);
控制器类:
@RestController()
public class HomeController
@Autowired
public HomeController(Map<String, MessageProcessor> processors, Map<String, ReconcileProcessor> reconcileProcessors,
@Qualifier("defaultProcessor") MessageProcessor defaultProcessor,
AuthenticationService authenticationService, ClientRepository repository,
@Value("$request.limit") int requestLimit)
// Here I receive XML
@GetMapping(value = "/v1/*")
public String message()
return "REST server";
@PostMapping(value = "/v1/token", consumes = MediaType.APPLICATION_XML_VALUE,
MediaType.APPLICATION_JSON_VALUE , produces = MediaType.APPLICATION_XML_VALUE,
MediaType.APPLICATION_JSON_VALUE )
public PaymentResponse handleMessage(@PathVariable("token") String token,
@RequestBody PaymentTransaction transaction, HttpServletRequest request) throws Exception
// Here I receive XML
@PostMapping(value = "/v1/notification")
public ResponseEntity<String> handleNotifications(@RequestBody Map<String, String> keyValuePairs)
// Here I receive key and value in request body
@PostMapping(value = "/v1/summary/by_date/token", consumes = MediaType.APPLICATION_XML_VALUE,
MediaType.APPLICATION_JSON_VALUE , produces = MediaType.APPLICATION_XML_VALUE,
MediaType.APPLICATION_JSON_VALUE )
public PaymentResponses handleReconcile(@PathVariable("token") String token, @RequestBody Reconcile reconcile,
HttpServletRequest request) throws Exception
// Here I receive XML
@ResponseStatus(value = HttpStatus.UNAUTHORIZED)
public static class UnauthorizedException extends RuntimeException
UnauthorizedException(String message)
super(message);
正如您所见,在某些方法中我收到 XML,而在其他方法中我收到 key=value&.....
形式的字符串
如何配置 Spring 以接受这两种类型? 我还应该将 Rest 控制器拆分为不同的文件吗?
编辑:
示例 XML 请求:
<?xml version="1.0" encoding="UTF-8"?>
<payment_transaction>
<transaction_type>authorize</transaction_type>
<transaction_id>2aeke4geaclv7ml80</transaction_id>
<amount>1000</amount>
<currency>USD</currency>
<card_number>22</card_number>
<shipping_address>
<first_name>Name</first_name>
</shipping_address>
</payment_transaction>
示例 XML 响应:
<?xml version="1.0" encoding="UTF-8"?>
<payment_response>
<transaction_type>authorize</transaction_type>
<status>approved</status>
<unique_id>5f7edd36689f03324f3ef531beacfaae</unique_id>
<transaction_id>asdsdlddea4sdaasdsdsa4dadasda</transaction_id>
<code>500</code>
<amount>101</amount>
<currency>EUR</currency>
</payment_response>
示例通知请求:
uniqueid=23434&type=sale&status=33
示例通知响应:它应该只返回 HTTP 状态 OK。
我用:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath />
</parent>
Java 版本:“10.0.2”2018-07-17
关于我使用的 XML 生成:
@XmlRootElement(name = "payment_transaction")
public class PaymentTransaction
public enum Response
failed_response, successful_response
@XmlElement(name = "transaction_type")
public String transactionType;
@XmlElement(name = "transaction_id")
public String transactionId;
@XmlElement(name = "usage")
POM 配置:https://pastebin.com/zXqYhDH3
【问题讨论】:
接受这两种格式是什么意思? 我的意思是:实现的 Rest 服务器应该接受 XML 请求和简单的键和值到请求正文中。 如何实现request interceptor,它可以在请求到达您的控制器或RequestBodyAdvice 之前对其进行操作?通过这种方式,您可以只有一个控制器来处理 XML 输入,在拦截器/通知中,您可以操纵请求正文以能够转发预期的 XML。 @m4gic 你能用工作示例粘贴官方答案吗? 如果您希望我这样做,请提供查询字符串版本和 XML 版本的示例请求。请求参数和请求正文也很重要。请显示预期的输出(例如 XML,应该从请求中解析)。我也想知道 spring-boot 版本和 JDK 版本。但是我只能在晚上创建一个示例。 【参考方案1】:更新此解决方案适用于 pre-2.x Spring-boot 版本。 另一件要考虑的事情是,在我的测试期间,我在我的 DTO(JacksonXmlRootElement、JacksonXmlProperty)上使用了 Jackson 的 XML 注释,也许 FormHttpMessageConverter 可以处理带有标准 JAXB 注释的 DTO(请参阅我对 Spring 2.0.4-RELEASE 的回答) - 你可以如果可以的话,最好朝着那个方向前进(或者至少在应用草图解决方案之前尝试一下)。
这是我的解决方案。我放弃了 RequestIntereptor(因为那是为了检查请求而不是修改它)和 RequestBodyAdvice(因为事实证明有更好的方法。
如果您查看available MessageConverters,您会发现读取已发布表单数据的唯一MessageConverter 是FormHttpMessageConverter。 这个类的问题在于返回类型,即Multivaluemap
但是,使用这个类作为基础,我创建了一个抽象类,它将表单数据读取到这个 Multivaluemap,并且只有一个您必须在子类中实现的抽象功能:它将根据值创建一个对象存储在多值映射中。
不幸的是,我不得不在您想阅读的 DTO 上引入一个接口(因为我保留了编写部分的原始实现,只是采用它)。
总而言之,我的工作解决方案:
在 WebMvcConfigurerAdapter 类中,我有这个配置:
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters)
MyRequestBodyHttpMessageConverter converter = new MyRequestBodyHttpMessageConverter();
//FormHttpMessageConverter converter = new FormHttpMessageConverter();
MediaType utf8FormEncoded = new MediaType("application","x-www-form-urlencoded", Charset.forName("UTF-8"));
//MediaType mediaType = MediaType.APPLICATION_FORM_URLENCODED; maybe UTF-8 is not needed
//converter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_FORM_URLENCODED));
converter.setSupportedMediaTypes(Arrays.asList(utf8FormEncoded));
converters.add(converter);
converters.add(new MappingJackson2HttpMessageConverter());
converters.add(new MappingJackson2XmlHttpMessageConverter());
super.configureMessageConverters(converters);
我修改了一点你的控制器功能:
@PostMapping(value = "/v1/token",
consumes = MediaType.APPLICATION_XML_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public @ResponseBody MyResponseBody handleMessage(@PathVariable("token") String token, @RequestBody MyRequestBody transaction, HttpServletRequest request) throws Exception
MyResponseBody body = new MyResponseBody();
body.setId(transaction.getId());
body.setName("received " + transaction.getName());
return body;
// check @ModelAttribute workaround https://***.com/questions/4339207/http-post-with-request-content-type-form-not-working-in-spring-mvc-3
@PostMapping(consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, value = "/v1/notification")
public ResponseEntity<String> handleNotifications(@ModelAttribute MyRequestBody transaction)
return new ResponseEntity<String>(HttpStatus.OK);
(下一部分导入包是有意义的,一些邮件api类可以在其他地方找到)
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.mail.internet.MimeUtility;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.StreamingHttpOutputMessage;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.converter.ResourceHttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MimeTypeUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
/**
* based on @link org.springframework.http.converter.FormHttpMessageConverter
*
* it uses the readed MultiValueMap to build up the DTO we would like to get from the request body.
*/
public abstract class AbstractRequestBodyFormHttpMessageConverter<T extends RequestParamSupport> implements HttpMessageConverter<T>
/**
* This is the only method you have to implement for your DTO class
* the class must implement RequestParamSupport
*/
protected abstract T buildObject(MultiValueMap<String, Object> valueMap);
public interface RequestParamSupport
MultiValueMap<String, Object> getRequestParams();
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private List<MediaType> supportedMediaTypes = new ArrayList<MediaType>();
private List<HttpMessageConverter<?>> partConverters = new ArrayList<HttpMessageConverter<?>>();
private Charset charset = DEFAULT_CHARSET;
private Charset multipartCharset;
private Class<T> bodyClass;
public AbstractRequestBodyFormHttpMessageConverter(Class<T> bodyClass)
this.bodyClass = bodyClass;
this.supportedMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
this.supportedMediaTypes.add(MediaType.MULTIPART_FORM_DATA);
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setWriteAcceptCharset(false); // see SPR-7316
this.partConverters.add(new ByteArrayHttpMessageConverter());
this.partConverters.add(stringHttpMessageConverter);
this.partConverters.add(new ResourceHttpMessageConverter());
applyDefaultCharset();
/**
* Set the character set to use when writing multipart data to encode file
* names. Encoding is based on the encoded-word syntax defined in RFC 2047
* and relies on @code MimeUtility from "javax.mail".
* <p>If not set file names will be encoded as US-ASCII.
* @since 4.1.1
* @see <a href="http://en.wikipedia.org/wiki/MIME#Encoded-Word">Encoded-Word</a>
*/
public void setMultipartCharset(Charset charset)
this.multipartCharset = charset;
/**
* Apply the configured charset as a default to registered part converters.
*/
private void applyDefaultCharset()
for (HttpMessageConverter<?> candidate : this.partConverters)
if (candidate instanceof AbstractHttpMessageConverter)
AbstractHttpMessageConverter<?> converter = (AbstractHttpMessageConverter<?>) candidate;
// Only override default charset if the converter operates with a charset to begin with...
if (converter.getDefaultCharset() != null)
converter.setDefaultCharset(this.charset);
@Override
public boolean canRead(Class<?> clazz, MediaType mediaType)
if (!bodyClass.isAssignableFrom(clazz))
return false;
if (mediaType == null)
return true;
for (MediaType supportedMediaType : getSupportedMediaTypes())
// We can't read multipart....
if (!supportedMediaType.equals(MediaType.MULTIPART_FORM_DATA) && supportedMediaType.includes(mediaType))
return true;
return false;
@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType)
if (!bodyClass.isAssignableFrom(clazz))
return false;
if (mediaType == null || MediaType.ALL.equals(mediaType))
return true;
for (MediaType supportedMediaType : getSupportedMediaTypes())
if (supportedMediaType.isCompatibleWith(mediaType))
return true;
return false;
/**
* Set the list of @link MediaType objects supported by this converter.
*/
public void setSupportedMediaTypes(List<MediaType> supportedMediaTypes)
this.supportedMediaTypes = supportedMediaTypes;
@Override
public List<MediaType> getSupportedMediaTypes()
return Collections.unmodifiableList(this.supportedMediaTypes);
@Override
public T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException
MediaType contentType = inputMessage.getHeaders().getContentType();
Charset charset = (contentType.getCharset() != null ? contentType.getCharset() : this.charset);
String body = StreamUtils.copyToString(inputMessage.getBody(), charset);
String[] pairs = StringUtils.tokenizeToStringArray(body, "&");
MultiValueMap<String, Object> result = new LinkedMultiValueMap<String, Object>(pairs.length);
for (String pair : pairs)
int idx = pair.indexOf('=');
if (idx == -1)
result.add(URLDecoder.decode(pair, charset.name()), null);
else
String name = URLDecoder.decode(pair.substring(0, idx), charset.name());
String value = URLDecoder.decode(pair.substring(idx + 1), charset.name());
result.add(name, value);
return buildObject(result);
@Override
public void write(T object, MediaType contentType,
HttpOutputMessage outputMessage) throws IOException,
HttpMessageNotWritableException
if (!isMultipart(object, contentType))
writeForm(object.getRequestParams(), contentType, outputMessage);
else
writeMultipart(object.getRequestParams(), outputMessage);
private boolean isMultipart(RequestParamSupport object, MediaType contentType)
if (contentType != null)
return MediaType.MULTIPART_FORM_DATA.includes(contentType);
MultiValueMap<String, Object> map = object.getRequestParams();
for (String name : map.keySet())
for (Object value : map.get(name))
if (value != null && !(value instanceof String))
return true;
return false;
private void writeForm(MultiValueMap<String, Object> form, MediaType contentType,
HttpOutputMessage outputMessage) throws IOException
Charset charset;
if (contentType != null)
outputMessage.getHeaders().setContentType(contentType);
charset = (contentType.getCharset() != null ? contentType.getCharset() : this.charset);
else
outputMessage.getHeaders().setContentType(MediaType.APPLICATION_FORM_URLENCODED);
charset = this.charset;
StringBuilder builder = new StringBuilder();
for (Iterator<String> nameIterator = form.keySet().iterator(); nameIterator.hasNext();)
String name = nameIterator.next();
for (Iterator<Object> valueIterator = form.get(name).iterator(); valueIterator.hasNext();)
String value = (String) valueIterator.next();
builder.append(URLEncoder.encode(name, charset.name()));
if (value != null)
builder.append('=');
builder.append(URLEncoder.encode(value, charset.name()));
if (valueIterator.hasNext())
builder.append('&');
if (nameIterator.hasNext())
builder.append('&');
final byte[] bytes = builder.toString().getBytes(charset.name());
outputMessage.getHeaders().setContentLength(bytes.length);
if (outputMessage instanceof StreamingHttpOutputMessage)
StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
streamingOutputMessage.setBody(new StreamingHttpOutputMessage.Body()
@Override
public void writeTo(OutputStream outputStream) throws IOException
StreamUtils.copy(bytes, outputStream);
);
else
StreamUtils.copy(bytes, outputMessage.getBody());
private void writeMultipart(final MultiValueMap<String, Object> parts, HttpOutputMessage outputMessage) throws IOException
final byte[] boundary = generateMultipartBoundary();
Map<String, String> parameters = Collections.singletonMap("boundary", new String(boundary, "US-ASCII"));
MediaType contentType = new MediaType(MediaType.MULTIPART_FORM_DATA, parameters);
HttpHeaders headers = outputMessage.getHeaders();
headers.setContentType(contentType);
if (outputMessage instanceof StreamingHttpOutputMessage)
StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
streamingOutputMessage.setBody(new StreamingHttpOutputMessage.Body()
@Override
public void writeTo(OutputStream outputStream) throws IOException
writeParts(outputStream, parts, boundary);
writeEnd(outputStream, boundary);
);
else
writeParts(outputMessage.getBody(), parts, boundary);
writeEnd(outputMessage.getBody(), boundary);
private void writeParts(OutputStream os, MultiValueMap<String, Object> parts, byte[] boundary) throws IOException
for (Map.Entry<String, List<Object>> entry : parts.entrySet())
String name = entry.getKey();
for (Object part : entry.getValue())
if (part != null)
writeBoundary(os, boundary);
writePart(name, getHttpEntity(part), os);
writeNewLine(os);
@SuppressWarnings("unchecked")
private void writePart(String name, HttpEntity<?> partEntity, OutputStream os) throws IOException
Object partBody = partEntity.getBody();
Class<?> partType = partBody.getClass();
HttpHeaders partHeaders = partEntity.getHeaders();
MediaType partContentType = partHeaders.getContentType();
for (HttpMessageConverter<?> messageConverter : this.partConverters)
if (messageConverter.canWrite(partType, partContentType))
HttpOutputMessage multipartMessage = new MultipartHttpOutputMessage(os);
multipartMessage.getHeaders().setContentDispositionFormData(name, getFilename(partBody));
if (!partHeaders.isEmpty())
multipartMessage.getHeaders().putAll(partHeaders);
((HttpMessageConverter<Object>) messageConverter).write(partBody, partContentType, multipartMessage);
return;
throw new HttpMessageNotWritableException("Could not write request: no suitable HttpMessageConverter " +
"found for request type [" + partType.getName() + "]");
/**
* Generate a multipart boundary.
* <p>This implementation delegates to
* @link MimeTypeUtils#generateMultipartBoundary().
*/
protected byte[] generateMultipartBoundary()
return MimeTypeUtils.generateMultipartBoundary();
/**
* Return an @link HttpEntity for the given part Object.
* @param part the part to return an @link HttpEntity for
* @return the part Object itself it is an @link HttpEntity,
* or a newly built @link HttpEntity wrapper for that part
*/
protected HttpEntity<?> getHttpEntity(Object part)
return (part instanceof HttpEntity ? (HttpEntity<?>) part : new HttpEntity<Object>(part));
/**
* Return the filename of the given multipart part. This value will be used for the
* @code Content-Disposition header.
* <p>The default implementation returns @link Resource#getFilename() if the part is a
* @code Resource, and @code null in other cases. Can be overridden in subclasses.
* @param part the part to determine the file name for
* @return the filename, or @code null if not known
*/
protected String getFilename(Object part)
if (part instanceof Resource)
Resource resource = (Resource) part;
String filename = resource.getFilename();
if (filename != null && this.multipartCharset != null)
filename = MimeDelegate.encode(filename, this.multipartCharset.name());
return filename;
else
return null;
private void writeBoundary(OutputStream os, byte[] boundary) throws IOException
os.write('-');
os.write('-');
os.write(boundary);
writeNewLine(os);
private static void writeEnd(OutputStream os, byte[] boundary) throws IOException
os.write('-');
os.write('-');
os.write(boundary);
os.write('-');
os.write('-');
writeNewLine(os);
private static void writeNewLine(OutputStream os) throws IOException
os.write('\r');
os.write('\n');
/**
* Implementation of @link org.springframework.http.HttpOutputMessage used
* to write a MIME multipart.
*/
private static class MultipartHttpOutputMessage implements HttpOutputMessage
private final OutputStream outputStream;
private final HttpHeaders headers = new HttpHeaders();
private boolean headersWritten = false;
public MultipartHttpOutputMessage(OutputStream outputStream)
this.outputStream = outputStream;
@Override
public HttpHeaders getHeaders()
return (this.headersWritten ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers);
@Override
public OutputStream getBody() throws IOException
writeHeaders();
return this.outputStream;
private void writeHeaders() throws IOException
if (!this.headersWritten)
for (Map.Entry<String, List<String>> entry : this.headers.entrySet())
byte[] headerName = getAsciiBytes(entry.getKey());
for (String headerValueString : entry.getValue())
byte[] headerValue = getAsciiBytes(headerValueString);
this.outputStream.write(headerName);
this.outputStream.write(':');
this.outputStream.write(' ');
this.outputStream.write(headerValue);
writeNewLine(this.outputStream);
writeNewLine(this.outputStream);
this.headersWritten = true;
private byte[] getAsciiBytes(String name)
try
return name.getBytes("US-ASCII");
catch (UnsupportedEncodingException ex)
// Should not happen - US-ASCII is always supported.
throw new IllegalStateException(ex);
/**
* Inner class to avoid a hard dependency on the JavaMail API.
*/
private static class MimeDelegate
public static String encode(String value, String charset)
try
return MimeUtility.encodeText(value, charset, null);
catch (UnsupportedEncodingException ex)
throw new IllegalStateException(ex);
bean转换器实现
public class MyRequestBodyHttpMessageConverter extends
AbstractRequestBodyFormHttpMessageConverter<MyRequestBody>
public MyRequestBodyHttpMessageConverter()
super(MyRequestBody.class);
@Override
protected MyRequestBody buildObject(MultiValueMap<String, Object> valueMap)
MyRequestBody parsed = new MyRequestBody();
parsed.setId(Long.valueOf((String)valueMap.get("id").get(0)));
parsed.setName((String)valueMap.get("name").get(0));
parsed.setRequestParams(valueMap);
return parsed;
最后是 MyRequestBody DTO(MyRequestBody 相同,只是名称不同)
@JacksonXmlRootElement
public class MyRequestBody implements RequestParamSupport, Serializable
@JsonIgnore
private transient MultiValueMap<String, Object> requestParams;
@JacksonXmlProperty
private Long id;
@JacksonXmlProperty
private String name;
//empty constructor, getters, setters, tostring, etc
@Override
public MultiValueMap<String, Object> getRequestParams()
return requestParams;
** 最后我的答案:**
如何配置 Spring 以接受这两种类型?
如您所见,您的 bean 转换器必须有自己的表单数据。 (不要忘记从表单数据映射时必须使用@ModelAttribute,而不是@RequestBody。)
我还应该将 Rest 控制器拆分为不同的文件吗?
不,这不是必需的,只需注册您的转换器。
【讨论】:
唯一的问题是,这不会发生在“幕后”,你必须在 AbstractRequestBodyFormHttpMessageConverter 子类中实现你的映射并且你必须注册它才能使用。但至少它有效。 另外我必须告诉你,我只测试了 application/x-www-form-urlencoded 标头,而不是 multipart/form-data。【参考方案2】:对于 Spring boot 2.0.4-RELEASE,看来你不需要做很多事情。
我做了这个配置:
@Configuration
public class WebConfiguration implements WebMvcConfigurer
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters)
//MyRequestBodyHttpMessageConverter converter = new MyRequestBodyHttpMessageConverter();
FormHttpMessageConverter converter = new FormHttpMessageConverter();
//MediaType utf8FormEncoded = new MediaType("application","x-www-form-urlencoded", Charset.forName("UTF-8"));
//MediaType mediaType = MediaType.APPLICATION_FORM_URLENCODED; maybe UTF-8 is not needed
converter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_FORM_URLENCODED));
//converter.setSupportedMediaTypes(Arrays.asList(utf8FormEncoded));
converters.add(converter);
MappingJackson2HttpMessageConverter conv1 = new MappingJackson2HttpMessageConverter();
conv1.getObjectMapper().registerModule(new JaxbAnnotationModule());
converters.add(conv1);
MappingJackson2XmlHttpMessageConverter conv = new MappingJackson2XmlHttpMessageConverter();
// required by jaxb annotations
conv.getObjectMapper().registerModule(new JaxbAnnotationModule());
converters.add(conv);
我用过你的 DTO:
@XmlRootElement(name = "payment_transaction")
public class PaymentTransaction
@XmlElement(name = "transaction_type")
public String transactionType;
@XmlElement(name = "transaction_id")
public String transactionId;
public String getTransactionType()
return transactionType;
public void setTransactionType(String transactionType)
this.transactionType = transactionType;
public String getTransactionId()
return transactionId;
public void setTransactionId(String transactionId)
this.transactionId = transactionId;
@Override
public String toString()
return "PaymentTransaction [transactionType=" + transactionType
+ ", transactionId=" + transactionId + "]";
控制器:
@RestController
public class MyController
/**
* https://***.com/questions/34782025/http-post-request-with-content-type-application-x-www-form-urlencoded-not-workin/38252762#38252762
*/
@PostMapping(value = "/v1/token",
consumes = MediaType.APPLICATION_XML_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public @ResponseBody PaymentTransaction handleMessage(@PathVariable("token") String token,
@RequestBody PaymentTransaction transaction, HttpServletRequest request) throws Exception
System.out.println("handleXmlMessage");
System.out.println(transaction);
PaymentTransaction body = new PaymentTransaction();
body.setTransactionId(transaction.getTransactionId());
body.setTransactionType("received: " + transaction.getTransactionType());
return body;
@PostMapping(consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, value = "/v1/notification")
public ResponseEntity<String> handleNotifications(@ModelAttribute PaymentTransaction transaction)
System.out.println("handleFormMessage");
System.out.println(transaction);
return new ResponseEntity<String>(HttpStatus.OK);
唯一要记住的主要事情是,似乎用解析的数据填充 DTO 是通过反射发生的:
供您参考
<payment_transaction>
<transaction_id>1</transaction_id>
<transaction_type>name</transaction_type>
</payment_transaction>
我收到了这个回复(查看我的控制器):
"transactionType": "received: null",
"transactionId": null
但是当我更改为DTO的字段名称时,它开始起作用(根元素无关紧要,有趣):
<payment_transaction>
<transactionId>1</transactionId>
<transactionType>name</transactionType>
</payment_transaction>
结果:
"transactionType": "received: name",
"transactionId": "1"
查询字符串也是如此。我不知道要更改什么才能让 spring 使用 @XmlRootElement/@XmlElement 中定义的名称来解析 xml。
【讨论】:
让我们continue this discussion in chat.【参考方案3】:这是另一种解决方案(对我来说效果很好),使用较少的 Spring 魔法并使用 HttpServletRequestWrapper 的旧方法。
在 WebMvcConfigurerAdapter 类中,现在我们不需要 MessageConverter:
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters)
//MyRequestBodyHttpMessageConverter converter = new MyRequestBodyHttpMessageConverter();
//FormHttpMessageConverter converter = new FormHttpMessageConverter();
//MediaType utf8FormEncoded = new MediaType("application","x-www-form-urlencoded", Charset.forName("UTF-8"));
//MediaType mediaType = MediaType.APPLICATION_FORM_URLENCODED; maybe UTF-8 is not needed
//converter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_FORM_URLENCODED));
//converter.setSupportedMediaTypes(Arrays.asList(utf8FormEncoded));
//converters.add(converter);
converters.add(new MappingJackson2HttpMessageConverter());
converters.add(new MappingJackson2XmlHttpMessageConverter());
super.configureMessageConverters(converters);
其他一切都发生在这个(servlet)过滤器实现中:
@WebFilter("/v1/notification")
public class MyRequestBodyFilter implements Filter
private static class MyServletInputStream extends ServletInputStream
private ByteArrayInputStream buffer;
public MyServletInputStream(byte[] contents)
this.buffer = new ByteArrayInputStream(contents);
@Override
public int read() throws IOException
return buffer.read();
@Override
public boolean isFinished()
return buffer.available() == 0;
@Override
public boolean isReady()
return true;
@Override
public void setReadListener(ReadListener listener)
throw new RuntimeException("Not implemented");
private class MyHttpServletRequestWrapper extends HttpServletRequestWrapper
MyHttpServletRequestWrapper(HttpServletRequest request)
super(request);
@Override
public ServletInputStream getInputStream() throws IOException
// converting the request parameters to the pojo and serialize it to XML
// the drawback of this way that the xml will be parsed again somewhere later
long id = Long.parseLong(getRequest().getParameter("id"));
String name = getRequest().getParameter("name");
MyRequestBody body = new MyRequestBody();
body.setId(id);
body.setName(name);
return new MyServletInputStream(new XmlMapper().writeValueAsBytes(body));
@Override
public void init(FilterConfig filterConfig) throws ServletException
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException
HttpServletRequest httpRequest = (HttpServletRequest) request;
chain.doFilter(new MyHttpServletRequestWrapper(httpRequest), response);
@Override
public void destroy()
我的测试控制器中没有任何更改,因此方法的签名保持不变:
@PostMapping(value = "/v1/token",
consumes = MediaType.APPLICATION_XML_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public @ResponseBody MyResponseBody handleMessage(@PathVariable("token") String token, @RequestBody MyRequestBody transaction, HttpServletRequest request) throws Exception
MyResponseBody body = new MyResponseBody();
body.setId(transaction.getId());
body.setName("received " + transaction.getName());
return body;
@PostMapping(consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, value = "/v1/notification")
public ResponseEntity<String> handleNotifications(@ModelAttribute MyRequestBody transaction)
return new ResponseEntity<String>(HttpStatus.OK);
【讨论】:
我必须在 handleNotifications 上保留 APPLICATION_FORM_URLENCODED_VALUE 和 ModelAttribute,可能如果我在过滤器中将请求的 Content-type 修改为“application/xml”,它的工作方式与 handleMessage 相同(使用 RequestBody 和使用消耗 = MediaType.APPLICATION_XML_VALUE)。 非常感谢您的努力。从长远来看,您会推荐哪种解决方案?请查看更新后的帖子,了解您的上述问题。 我会选择对 Spring 更友好的方法,因为它应该更快(您不必创建稍后将解析的 xml)。如果 Spring 友好版本由于某种原因(类路径、jaxb/marhalling 问题)不起作用,我将使用第二种方法作为备用计划。在 servlet 包装器中,我使用了 XmlMapper,我认为它可以很好地与 Jackson 相关的 xml 注释一起使用,所以如果你使用它,也许你应该更改序列化。后者的一个优点是更通用并且不需要修改您的 DTO(无需使用接口进行扩展)。 你的意思是第一种方法? 是的,我会使用自定义 MessageConverter。为了让它工作,你需要在项目依赖项中添加一个 mail-api jar(你的 spring-boot 版本使用 1.6.1 版本)。以上是关于使用 RestController 接受字符串和 XML 数据的主要内容,如果未能解决你的问题,请参考以下文章
spring RESTcontroller 接受 dataURI
Spring @RestController,spring-boot 出现意外错误(类型=不可接受,状态=406)
Spring 的@Controller 和@RestController的区别