簡體   English   中英

使用RestController接受字符串和XML數據

[英]Accept Strings and XML data with RestController

我想創建REST服務器,它接受XML請求和純文本到不同的控制器。 我試圖實現這個:

@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&.....形式的String

我如何配置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

更新此解決方案適用於2.x之前的Spring-boot版本。 另外需要考慮的是,在我的測試中,我在我的DTO上使用了Jackson的XML注釋(JacksonXmlRootElement,JacksonXmlProperty),也許FormHttpMessageConverter可以使用標准的JAXB注釋處理DTO(請參閱我對Spring 2.0.4-RELEASE的回答) - 所以你可能會更好如果你可以去那個方向(或者至少在你應用草圖解決方案之前嘗試一下)。

這是我的解決方案。 我刪除了RequestIntereptor(因為這是為了檢查請求而不是修改它)和RequestBodyAdvice(因為它證明有更好的方法)。

如果您查看可用的MessageConverters,您會發現讀取已發布表單數據的唯一MessageConverter是FormHttpMessageConverter。 此類的問題是返回類型,即Multivaluemap

但是,使用這個類作為基礎,我創建了一個抽象類,它將表單數據讀取到這個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://stackoverflow.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轉換器。 (不要忘記在從表單數據映射而不是@RequestBody時必須使用@ModelAttribute。)

我還應該將Rest控制器拆分成不同的文件嗎?

不,這不是必需的,只需注冊您的轉換器。

這是另一種解決方案(對我來說效果很好),使用較少的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)Filter實現中:

@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);
}

對於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://stackoverflow.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中定義的名稱來解析xmls。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM