Spring RestTemplate解析自定义错误响应

12

给定一个REST服务调用

http://acme.com/app/widget/123

返回:

<widget>
  <id>123</id>
  <name>Foo</name>
  <manufacturer>Acme</manufacturer>
</widget>

这个客户端代码可以工作:

RestTemplate restTemplate = new RestTemplate();
XStreamMarshaller xStreamMarshaller = new XStreamMarshaller();
xStreamMarshaller.getXStream().processAnnotations(
    new Class[] { 
        Widget.class,
        ErrorMessage.class
    });

HttpMessageConverter<?> marshallingConverter = new MarshallingHttpMessageConverter(
    xStreamMarshaller, xStreamMarshaller);

List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
    converters.add(marshallingConverter);

restTemplate.setMessageConverters(converters);

Widget w = restTemplate.getForObject(
    "http://acme.com/app/widget/{id}", Widget.class, 123L);

然而,调用 http://acme.com/app/widget/456 返回:
<error>
    <message>Widget 456 does not exist</message>
    <timestamp>Wed, 12 Mar 2014 10:34:37 GMT</timestamp>
</error>

但是这个客户端代码会抛出一个异常:

Widget w = restTemplate.getForObject(
    "http://acme.com/app/widget/{id}", Widget.class, 456L);

org.springframework.web.client.HttpClientErrorException: 404 Not Found

我尝试过:

try {
    Widget w = restTemplate.getForObject(
       "http://acme.com/app/widget/{id}", Widget.class, 456L);
}
catch (HttpClientErrorException e) {
    ErrorMessage errorMessage = restTemplate.getForObject(
       "http://acme.com/app/widget/{id}", ErrorMessage.class, 456L);

   // etc...
}

第二次调用只是抛出了另一个 HttpClientErrorException 异常,而且调用服务两次不太合适。有没有一种方法能够仅调用一次服务,在成功时将响应解析为 Widget,在未找到时解析为 ErrorMessage?

我不确定你为什么关心从XML响应中获取精确的错误消息,因为它实际上与HTTP 404 - 未找到相同。只需捕获404并抛出新异常或生成自己的消息即可。 - nickdos
如果成功解析了此自定义消息,我就知道小部件未被找到,而不是服务已停止或正在调用服务的错误URL。 - Paul Croarkin
好的,尽管“服务停止”应该是不同的代码(500)。我建议从这里开始阅读 RestTemplate 的源代码…… 你确定 HTTP 正文没有被存储在“HttpClientErrorException”中吗? - nickdos
你可以访问下面的线程。它包含有完整工作代码及其描述: https://dev59.com/V1oT5IYBdhLWcg3w2SYT#51805956 - Md. Sajedul Karim
4个回答

13

基于我的评论,我查阅了 HttpClientErrorException JavaDoc,它确实支持设置/获取 statusTextresponseBody。然而,它们是可选的,RestTemplate 可能不会填充它们——你需要尝试类似以下的操作:

try {
    Widget w = restTemplate.getForObject(
       "http://acme.com/app/widget/{id}", Widget.class, 456L);
}
catch (HttpClientErrorException e) {
    String responseBody = e.getResponseBodyAsString();
    String statusText = e.getStatusText();
    // log or process either of these...
    // you'll probably have to unmarshall the XML manually (only 2 fields so easy)
}

如果它们都是空的/ null,那么您可能需要扩展涉及RestTemplate类并自己填充这些字段和/或在Spring网站上提出Jira问题。


这听起来很有前途,但是并没有抛出 HttpClientErrorException 异常。相反,我得到了 org.springframework.beans.TypeMismatchException:无法将类型为 'com.acme.ErrorMessage' 的值转换为没有响应体的所需类型 'com.acme.Widget'。我会看一下如何扩展 RestTemplate。感谢您的建议! - Paul Croarkin
1
你最初的问题中提到了“但是这个客户端代码抛出了一个异常:HttpClientErrorException。有什么变化吗?” 我建议当抛出这个异常时,你在那里处理它 - 也许我的代码有误导性。 - nickdos
@PaulCroarkin,你有没有找到反序列化错误主体的解决方案? - Basil
我有同样的问题。在API服务中,我返回一个ApiError对象,该对象具有错误代码(例如:423)和错误消息(例如:访问被拒绝)作为字段。当从浏览器执行时,它会正确地返回响应中的JSON消息,但是当我使用restTemplate.postForObject调用相同的内容时,它会抛出HttpClientErrorException,其中包含正确的错误代码,但不包含错误消息。 - KayKay

8
您还可以根据需要从错误响应主体创建对象:

ObjectMapper om = new ObjectMapper();
String errorResponse = ex.getResponseBodyAsString();

MyClass obj = om.readValue(errorResponse, MyClass.class);

1
Jackson的ObjectMapper不是关于JSON而不是XML吗?问题是关于XML而不是JSON。 - peterh

2

由于您已经捕获到HttpClientErrorException对象,因此它应该允许您轻松提取有关错误的有用信息并将其传递给调用者。

例如:

try{
    <call to rest endpoint using RestTemplate>
} catch(HttpClientErrorException e){
    HttpStatus statusCode = e.getStatusCode();
    String body = e.getResponseBodyAsString();
}

我认为如果需要进一步将错误信息body反序列化为相关对象,则可以在catch语句范围之外的某个地方处理。


1

我也发现了Spring库中这个令人不安的变化。您以前可以抛出一个ResponseStatusException,并传递HttpStatus代码和自定义消息,然后捕获HttpClientErrorException并简单地打印getMessage()以查看自定义错误。但现在这种方法不再起作用了。为了打印自定义错误,我必须将ResponseBody捕获为String,在HttpClientErrorException上使用getResponseBodyAsString。然后我需要将其解析为字符串,并进行一些相当奇怪的子字符串操作。这样做会剥离掉ResponseBody中的信息,并给出服务器发送的消息。以下是执行此操作的代码:

String message = hce.getResponseBodyAsString();
int start, end;
start =  message.lastIndexOf(":", message.lastIndexOf(",")-1) + 2;
end = message.lastIndexOf(",") -1;
message = message.substring(start, end);
System.out.println(message);

当我使用ARC或Postman进行测试时,它们可以在我在服务器上的application.properties文件中添加server.error.include-message=always后显示正确的消息。我不确定他们使用的是什么方法来提取消息,但知道这一点会很好。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接