将RestTemplate响应映射到java Object

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了将RestTemplate响应映射到java Object相关的知识,希望对你有一定的参考价值。

我正在使用RestTemplate从远程休息服务获取数据,我的代码是这样的。

ResponseEntity<List<MyObject >> responseEntity = restTemplate.exchange(request, responseType);

但是,如果没有结果,我的上面代码行将抛出异常,那么休息服务将只返回文本消息,说没有找到记录。我可以先将结果映射到字符串,然后使用Jackson 2 ObjectMapper映射到MyObject

ResponseEntity<String> responseEntity = restTemplate.exchange(request, responseType);
String jsonInput= response.getBody();
List<MyObject> myObjects = objectMapper.readValue(jsonInput, new TypeReference<List<MyObject>>(){});

但我不喜欢这种方法。对此有没有更好的解决方案。

答案

首先,您可以为整个API编写一个包装器。使用@Component对其进行注释,您可以通过Springs DI在任何地方使用它。看看this example project,它显示了使用swagger codegen为resttemplate客户端生成的代码。

正如您所说,您尝试实现自定义responserrorhandler但没有成功,我假设API返回响应主体"no record found",而状态代码为200

因此,您可以创建我的第二个答案中提到的自定义AbstractHttpMessageConverter。因为你正在使用使用带有jackson的objectmapper的spring resttemplate,所以我们不必使用这个非常通用的超类来创建我们自己的。我们可以使用和扩展更适合的AbstractJackson2HttpMessageConverter类。特定用例的实现可能如下所示:

import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;

public class WeirdAPIJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {
    public static final String NO_RECORD_FOUND = "no record found";

    public WeirdAPIJackson2HttpMessageConverter() {
        // Create another constructor if you want to pass an already existing ObjectMapper
        // Currently this HttpMessageConverter is applied for every MediaType, this is application-dependent
        super(new ObjectMapper(), MediaType.ALL);
    }

    @Override
    public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        try (BufferedReader br = new BufferedReader(new InputStreamReader(inputMessage.getBody(), DEFAULT_CHARSET))) {
            String responseBodyStr = br.lines().collect(Collectors.joining(System.lineSeparator()));
            if (NO_RECORD_FOUND.equals(responseBodyStr)) {
                JavaType javaType = super.getJavaType(type, contextClass);
                if(Collection.class.isAssignableFrom(javaType.getRawClass())){
                    return Collections.emptyList();
                } else if( Map.class.isAssignableFrom(javaType.getRawClass())){
                   return Collections.emptyMap();
                }
                return null;
            }
        }
        return super.read(type, contextClass, inputMessage);
    }
}

自定义HttpMessageConverter正在检查特定“未找到记录”的响应正文。如果是这种情况,我们会尝试根据泛型返回类型返回默认值。如果返回类型是Collection的子类型,则Atm返回空列表,Set的空集和所有其他Class类型的null。

此外,我使用MockRestServiceServer创建了一个RestClientTest,以演示如何在上述API包装器组件中使用RestTemplate以及如何将其设置为使用我们的自定义AbstractJackson2HttpMessageConverter。

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.client.RestClientTest;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.client.ExpectedCount;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.client.RestTemplate;

import java.util.List;
import java.util.Optional;

import static org.junit.Assert.*;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus;

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {RestTemplateResponseErrorHandlerIntegrationTest.MyObject.class})
@RestClientTest
public class RestTemplateResponseErrorHandlerIntegrationTest {

    static class MyObject {
        // This just refers to your MyObject class which you mentioned in your answer
    }

    private final static String REQUEST_API_URL = "/api/myobjects/";
    private final static String REQUEST_API_URL_SINGLE = "/api/myobjects/1";

    @Autowired
    private MockRestServiceServer server;

    @Autowired
    private RestTemplateBuilder builder;


    @Test
    public void test_custom_converter_on_weird_api_response_list() {
        assertNotNull(this.builder);
        assertNotNull(this.server);

        RestTemplate restTemplate = this.builder
                .messageConverters(new WeirdAPIJackson2HttpMessageConverter())
                .build();

        this.server.expect(ExpectedCount.once(), requestTo(REQUEST_API_URL))
                .andExpect(method(HttpMethod.GET))
                .andRespond(withStatus(HttpStatus.OK).body(WeirdAPIJackson2HttpMessageConverter.NO_RECORD_FOUND));

        this.server.expect(ExpectedCount.once(), requestTo(REQUEST_API_URL_SINGLE))
                .andExpect(method(HttpMethod.GET))
                .andRespond(withStatus(HttpStatus.OK).body(WeirdAPIJackson2HttpMessageConverter.NO_RECORD_FOUND));


        ResponseEntity<List<MyObject>> response = restTemplate.exchange(REQUEST_API_URL,
                HttpMethod.GET,
                null,
                new ParameterizedTypeReference<List<MyObject>>() {
                });

        assertNotNull(response.getBody());
        assertTrue(response.getBody().isEmpty());

        Optional<MyObject> myObject = Optional.ofNullable(restTemplate.getForObject(REQUEST_API_URL_SINGLE, MyObject.class));
        assertFalse(myObject.isPresent());

        this.server.verify();
    }
}
另一答案

我通常在使用restTemplate的项目中做的是将响应保存在java.util.Map中并创建一个方法,在我想要的对象中转换该Map。也许在像Map这样的抽象对象中保存响应可以帮助您解决该异常问题。

例如,我发出这样的请求:

List<Map> list = null;
List<MyObject> listObjects = new ArrayList<MyObject>();
HttpHeaders headers = new HttpHeaders();
HttpEntity<String> entity = new HttpEntity<>(headers);

ResponseEntity<Map> response = restTemplate.exchange(url, HttpMethod.GET, entity, Map.class);

if (response != null && response.getStatusCode().value() == 200) {
    list = (List<Map>) response.getBody().get("items"); // this depends on the response
    for (Map item : list) { // we iterate for each one of the items of the list transforming it
        MyObject myObject = transform(item);
        listObjects.add(myObject);
    }
}

函数transform()是我自己创建的一个自定义方法:MyObject transform(Map item);,它接收一个Map对象并返回我想要的对象。您可以检查是否首先找不到记录,而不是调用方法转换。

以上是关于将RestTemplate响应映射到java Object的主要内容,如果未能解决你的问题,请参考以下文章

使用Spring RestTemplate将嵌套的JSON对象映射到Java类

spring restTemplate 动态映射

为啥 RestTemplate 不将响应表示绑定到 PagedResources?

如何在 Spring RestTemplate 中记录响应?

如何使用 spring 记录 RestTemplate 请求和响应?

尝试使用 Spring Boots 的 RestTemplate 使用内容类型 [text/json]