首页 > 解决方案 > Spring Boot 和 configureMessageConverters

问题描述

我面临与 Spring Boot + 数据转换器相关的问题。所以,基本上我想针对到达名为 /request/test.

我的配置文件

package com.my.package.configuration;

import com.test.AcceptMessageRequestType;
import com.test.AcceptMessageResultType;
import com.test.ObjectFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.xml.MarshallingHttpMessageConverter;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.connection.CachingConnectionFactory;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import javax.jms.Session;
import java.util.List;

@Configuration
@EnableWebMvc
public class ReqConfiguration implements WebMvcConfigurer {

    private static final Logger LOG =
            LoggerFactory.getLogger(ReqConfiguration.class);


    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(customConverter());
    }

/* NOT WORKING
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        try {
            converters.add(customConverter());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
*/

    @Bean
    public HttpMessageConverter<Object> customConverter() {
        MarshallingHttpMessageConverter xmlConverter = new MarshallingHttpMessageConverter();
        xmlConverter.setSupportedMediaTypes(List.of(MediaType.APPLICATION_XML));
        xmlConverter.setMarshaller(jaxb2Marshaller());
        xmlConverter.setUnmarshaller(jaxb2Marshaller());
        return xmlConverter;
    }


    @Bean
    public Jaxb2Marshaller jaxb2Marshaller() {
        Jaxb2Marshaller jaxb2Marshaller = new Jaxb2Marshaller();
        jaxb2Marshaller.setSchemas(
                new ClassPathResource("/MyMessageResponse.xsd"),
                new ClassPathResource("/MyMessageRequest.xsd"));
        jaxb2Marshaller.setClassesToBeBound(MyMessageRequest.class,
                MyMessageResponse.class);
        jaxb2Marshaller.setValidationEventHandler(event -> {
            event.getMessage();
            LOG.info("Do something");
        });
        jaxb2Marshaller.afterPropertiesSet();
        return jaxb2Marshaller;
    }

}

我的 pom 文件的相关部分

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-oxm</artifactId>
    </dependency>

    <!--<dependency> WORKS BUT NOT bypasses jaxb2Marshaller and no validation happens
        <groupId>com.fasterxml.jackson.dataformat</groupId>
        <artifactId>jackson-dataformat-xml</artifactId>
    </dependency>-->

休息端点

@PostMapping(path = "/request/test", consumes = MediaType.APPLICATION_XML_VALUE,
        produces = MediaType.APPLICATION_XML_VALUE)
public @ResponseBody MyResponseMessage acceptMessage(@RequestBody MyRequestMessage message) {

   return null;
}

xsd 生成的类(cxf-xjc-plugin 插件)

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "MyResponseMessage", propOrder = {
    "result",
    "uuid",
    "accepted"
})
public class MyResponseMessage implements Serializable {

    @XmlElement(required = true)
    protected Result result = new Result();
    @XmlElement(required = true)
    protected String uuid;
    protected boolean accepted;
     <... setter / getter cut out ...>
}

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "result", propOrder = {
    "result",
    "desc"
})
public class Result implements Serializable {

    protected int code;
    @XmlElement(required = true)
    protected String desc;
<... setter / getter cut out ...>
}

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "AcceptMessageRequestType", propOrder = {
    "id",
    "name",
    "state"
})

public class MyRequestMessage implements Serializable {

@XmlElement(required = true)
protected String id;
@XmlElement(required = true)
protected String name;
@XmlElement(required = true)
protected String state;
   <... setters / getters cut out...>
}

所以,我得到的只是 415(不支持的类型),或者如果我使用 jackson,我会得到响应,但传入请求的验证未针对 xsd 进行验证。如何使传入的 xml 请求针对 xsd 进行验证?

更新 1 号

似乎这个问题很相似,但由于某种原因它不起作用......

尝试的解决方案

标签: spring-boot

解决方案


[糟糕的解决方案开始]

好的,所以我所做的是

步骤 1

package com.my.package.configuration;

import com.test.AcceptMessageRequestType;
import com.test.AcceptMessageResultType;
import com.test.ObjectFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.xml.MarshallingHttpMessageConverter;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.connection.CachingConnectionFactory;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import javax.jms.Session;
import java.util.List;

@Configuration
@EnableWebMvc
public class ReqConfiguration implements WebMvcConfigurer {

    private static final Logger LOG =
            LoggerFactory.getLogger(ReqConfiguration.class);


    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(customConverter());
    }

@Override //ADD THIS INSTEAD
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    try {
        converters.add(customConverter());
    } catch (Exception e) {
        e.printStackTrace();
    }
}


/* NOT WORKING
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        try {
            converters.add(customConverter());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
*/

    @Bean
    public HttpMessageConverter<Object> customConverter() {
        MarshallingHttpMessageConverter xmlConverter = new MarshallingHttpMessageConverter();
        xmlConverter.setSupportedMediaTypes(List.of(MediaType.APPLICATION_XML));
        xmlConverter.setMarshaller(jaxb2Marshaller());
        xmlConverter.setUnmarshaller(jaxb2Marshaller());
        return xmlConverter;
    }


    @Bean
    public Jaxb2Marshaller jaxb2Marshaller() {
        Jaxb2Marshaller jaxb2Marshaller = new Jaxb2Marshaller();
        jaxb2Marshaller.setSchemas(
                new ClassPathResource("/MyMessageResponse.xsd"),
                new ClassPathResource("/MyMessageRequest.xsd"));
        jaxb2Marshaller.setClassesToBeBound(/*path to ObjectFactory.class(-es)*/);
        jaxb2Marshaller.setValidationEventHandler(event -> {
            event.getMessage();
            LOG.info("Do something");
        });
        //jaxb2Marshaller.afterPropertiesSet();
        return jaxb2Marshaller;
    }

}

步号 1.5 添加Maven条目

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-oxm</artifactId>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.dataformat</groupId>
        <artifactId>jackson-dataformat-xml</artifactId>
    </dependency>

第 2 步重构您的端点以接受字符串并返回字符串

@PostMapping(path = "/request/test", consumes = MediaType.APPLICATION_XML_VALUE,
        produces = MediaType.APPLICATION_XML_VALUE)
public @ResponseBody String acceptMessage(@RequestBody String message) {
        final XMLInputFactory inputFactory = XMLInputFactory.newInstance();
    final StringReader reader = new StringReader(message);
   jaxb2Marshaller.unmarshal(new StAXSource(inputFactory.createXMLStreamReader(reader)));
   return null;
}

现在一切都应该由 xsd 验证

[糟糕的解决方案结束]

所有需要做的就是

在我定义 jaxb2Marshaller 的块中添加 jaxb2Marshaller.setSupportJaxbElementClass(Boolean.TRUE);

并且只需将 JAXBElement 用于输入并相应地用于输出。

另外,删除 Maven 依赖项

    <dependency>
        <groupId>com.fasterxml.jackson.dataformat</groupId>
        <artifactId>jackson-dataformat-xml</artifactId>
    </dependency>

控制器变成

@PostMapping(path = "/request/test", consumes = MediaType.APPLICATION_XML_VALUE,
        produces = MediaType.APPLICATION_XML_VALUE)
public @ResponseBody JAXBElement<OUTPUT.class> acceptMessage(@RequestBody JAXBElement<INPUT.class> message) {
// do something with a validated message object
}

解决方案基于这篇文章Spring RestTemplate not accepting JAXBElement

更新 1 号

Spring Boot 2.4.0 不需要@EnableWebMvc,可以跳过


推荐阅读