Spring RestTemplate缺少自定义HttpMessageConverter,即使已在配置中添加了该转换器

zc0qhyus  于 2022-10-30  发布在  Spring
关注(0)|答案(1)|浏览(161)

(前提:问题就在最后)
我使用Sping Boot 2.7.4创建了一个新项目,它使用了Spring Web。
我必须为Json对象使用一个定制库,所以我为表示这些Json对象的类创建了一个定制的HttpMessageConverter
我在Spring配置类中添加了该转换器,但是当我运行测试时,我使用的RestTemplate似乎由于某种原因没有使用它。
我得到的错误是这样的:

org.springframework.web.client.UnknownContentTypeException: Could not extract response: no suitable HttpMessageConverter found for response type [interface com.example.MyJsonClass] and content type [application/json;charset=UTF-8]
    at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:126)
    at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:1037)
    at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:1020)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:778)
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:711)
    at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:468)
    at org.springframework.boot.test.web.client.TestRestTemplate.postForEntity(TestRestTemplate.java:440)
    at com.example.Tests.test(Tests.java:61)

在给你看代码之前,我会提到我找到了一种方法来让它工作,但我不明白为什么需要它。我会在代码之后发布它。

下面是代码:
配置方式:

@SpringBootApplication
public class MyApplication implements WebMvcConfigurer {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
        // adding the custom converter here
        messageConverters.add(new MyCustomHttpMessageConverter());
    }

    @Bean
    RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

自定义转换器(不能透露实现...):

public class MyCustomHttpMessageConverter implements HttpMessageConverter<MyJsonClass> {

    @Override
    public boolean canRead(Class<?> clazz, MediaType mediaType) {

        // implementation...
    }

    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {

        // implementation...
    }

    @Override
    public List<MediaType> getSupportedMediaTypes() {

        return List.of(MediaType.APPLICATION_JSON, MediaType.APPLICATION_JSON_UTF8);
    }

    @Override
    public MyJsonClass read(Class<? extends MyJsonClass> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {

        // implementation...
    }

    @Override
    public void write(MyJsonClass t, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {

        // implementation...
    }
}

控制器:

@Controller
@RequestMapping("/")
public class MyController {

    @Autowired
    private RestTemplate restTemplate;

    @PostMapping(path = "/test", consumes = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<MyJsonClass> test(@RequestBody MyJsonClass body) {

        System.out.println("test endpoint called.");
        // makes a remote call to an external service
        ResponseEntity<MyJsonClass> response = restTemplate.postForEntity(
                "http://localhost:8080/test",// this is normally a remote endpoint, I actually set it to localhost in the test configuration properties
                body, MyJsonClass.class);

        return ResponseEntity.ok(response.getBody());
    }
}

测试类别:

@AutoConfigureMockMvc
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class Tests {

    @Autowired
    private TestRestTemplate testRestTemplate;

    @Autowired
    private RestTemplate restTemplate;

    private MockRestServiceServer mockServer;
    private ObjectMapper mapper = new ObjectMapper();

    @BeforeEach
    public void init() {
        mockServer = MockRestServiceServer.createServer(restTemplate);
    }

    @Test
    public void test() throws Exception {

        MyJsonClass testPayload = new MyJsonClass("testProperty", "testValue");// just an example
        // I need to fake a request to a remote server
        mockServer.expect(ExpectedCount.once(),
                requestTo(new URI("http:localhost:8080/test")))
                .andExpect(method(HttpMethod.POST))
                .andExpect(content().string("{\"testProperty\":\"testValue\"}"))
                .andRespond(withStatus(HttpStatus.OK)
                        .contentType(MediaType.APPLICATION_JSON)
                        .body(mapper.writeValueAsString(testPayload.clone().set("testProperty2", "testValue2"))));

        ResponseEntity<MyJsonClass> response = testRestTemplate.postForEntity("/test", testPayload, MyJsonClass.class);

        // assertions here...
    }
}

我让它工作的方法是显式地将转换器添加到2 RestTemplate中。但我认为不需要它,因为我将它添加到了配置类的configureMessageConverters方法中。
restTemplate Bean的创建更改为:

@Bean
RestTemplate restTemplate() {
    RestTemplate restTemplate = new RestTemplate();
    restTemplate.getMessageConverters().add(new MyCustomHttpMessageConverter());
    return restTemplate;
}

在测试类中,在@BeforeEach中,我为TestRestTemplate添加了以下行:

testRestTemplate.getRestTemplate().getMessageConverters().add(new MyCustomHttpMessageConverter());

所以我猜最后一个问题是:如果我已经通过WebMvcConfigurer类的实现在配置中添加了转换器,为什么还必须显式地将其添加到每个RestTemplate中呢?(注意:我也尝试过使用extendMessageConverters方法,结果相同)。
或者:为什么RestTemplate没有使用我在配置中添加的转换器?

uajslkp6

uajslkp61#

首先,手动创建RestTemplate,而不使用Springbean注入。这样,Spring就不能自动连接任何配置。
您应该改用RestTemplateBuilder来使用Spring注入。

@Bean
RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
    return restTemplateBuilder.build();
}

然后,由于您使用的是Sping Boot ,因此不必通过WebMvcConfigurer注册转换器,而只需使用HttpMethodConverter注册bean即可

@Configuration
public class SpringConfiguration{
    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
        return restTemplateBuilder.build();
    }

    @Bean
    public HttpMessageConverter<MyJsonClass> myCustomHttpMessageConverter() {
        return new MyCustomHttpMessageConverter();
    }
}

这样,Spring应该首先使用HttpMessageConverter创建一个bean,然后自动将其注入到RestTemplateBuilder中。

相关问题