如何在返回多种类型XML的URL上使用Spring RestTemplate和JAXB编组

我需要对返回并始终返回状态代码200的服务进行Rest POST。 (蹩脚的第三方产品!)。

我的代码如下:

 Job job = getRestTemplate().postForObject(url, postData, Job.class); 

我的applicationContext.xml看起来像:

                 domain.fullspec.Job domain.fullspec.Exception    

当我尝试拨打此电话并且服务失败时,我得到:

  Failed to convert value of type 'domain.fullspec.Exception' to required type 'domain.fullspec.Job' 

在postForObject()调用中,我要求一个Job.class而不是一个,它正在变得烦恼。

我想我需要能够做一些事情:

 Object o = getRestTemplate().postForObject(url, postData, Object.class); if (o instanceof Job.class) { ... else if (o instanceof Exception.class) { } 

但这不起作用,因为JAXB抱怨它不知道如何编组到Object.class – 这并不奇怪。

我试图创建MarshallingHttpMessageConverter的子类并覆盖readFromSource()

protected Object readFromSource(Class clazz,HttpHeaders headers,Source source){

  Object o = null; try { o = super.readFromSource(clazz, headers, source); } catch (Exception e) { try { o = super.readFromSource(MyCustomException.class, headers, source); } catch (IOException e1) { log.info("Failed readFromSource "+e); } } return o; } 

不幸的是,这不起作用,因为在我重试它时,源内的基础输入流已经关闭。

感谢任何建议,

汤姆

更新:我通过获取inputStream的副本来实现此目的

 protected Object readFromSource(Class clazz, HttpHeaders headers, Source source) { InputStream is = ((StreamSource) source).getInputStream(); // Take a copy of the input stream so we can use it for initial JAXB conversion // and if that fails, we can try to convert to Exception CopyInputStream copyInputStream = new CopyInputStream(is); // input stream in source is empty now, so reset using copy ((StreamSource) source).setInputStream(copyInputStream.getCopy()); Object o = null; try { o = super.readFromSource(clazz, headers, source); // we have failed to unmarshal to 'clazz' - assume it is  and unmarshal to MyCustomException } catch (Exception e) { try { // reset input stream using copy ((StreamSource) source).setInputStream(copyInputStream.getCopy()); o = super.readFromSource(MyCustomException.class, headers, source); } catch (IOException e1) { e1.printStackTrace(); } e.printStackTrace(); } return o; } 

CopyInputStream取自http://www.velocityreviews.com/forums/t143479-how-to-make-a-copy-of-inputstream-object.html ,我会将其粘贴在此处。

 import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; public class CopyInputStream { private InputStream _is; private ByteArrayOutputStream _copy = new ByteArrayOutputStream(); /** * */ public CopyInputStream(InputStream is) { _is = is; try { copy(); } catch(IOException ex) { // do nothing } } private int copy() throws IOException { int read = 0; int chunk = 0; byte[] data = new byte[256]; while(-1 != (chunk = _is.read(data))) { read += data.length; _copy.write(data, 0, chunk); } return read; } public InputStream getCopy() { return (InputStream)new ByteArrayInputStream(_copy.toByteArray()); } } 

@Tom:我不认为创建自定义MarshallingHttpMessageConverter会对你有所帮助。 当服务失败时,内置转换器会返回正确的类(Exception类),但是RestTemplate不知道如何将Exception类返回给被调用者,因为您已将响应类型指定为Job类。

我阅读了RestTemplate源代码 ,您目前正在调用此API: –

 public  T postForObject(URI url, Object request, Class responseType) throws RestClientException { HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, responseType); HttpMessageConverterExtractor responseExtractor = new HttpMessageConverterExtractor(responseType, getMessageConverters()); return execute(url, HttpMethod.POST, requestCallback, responseExtractor); } 

如您所见,它根据您的响应类型返回类型T. 你可能需要做的是RestTemplate并添加一个新的postForObject() API,它返回一个Object而不是类型T,这样你就可以对返回的对象执行instanceof检查。

UPDATE

我一直在考虑这个问题的解决方案,而不是使用内置的RestTemplate ,为什么不自己编写呢? 我认为这比RestTemplate以添加新方法更好。

这是我的例子……授予,我没有测试这段代码,但它应该给你一个想法: –

 // reuse the same marshaller wired in RestTemplate @Autowired private Jaxb2Marshaller jaxb2Marshaller; public Object genericPost(String url) { // using Commons HttpClient HttpClient client = new HttpClient(); PostMethod method = new PostMethod(url); // add your data here method.addParameter("data", "your-data"); try { int returnCode = client.executeMethod(method); // status code is 200 if (returnCode == HttpStatus.SC_OK) { // using Commons IO to convert inputstream to string String xml = IOUtil.toString(method.getResponseBodyAsStream()); return jaxb2Marshaller.unmarshal(new StreamSource(new ByteArrayInputStream(xml.getBytes("UTF-8")))); } else { // handle error } } catch (Exception e) { throw new RuntimeException(e); } finally { method.releaseConnection(); } return null; } 

如果您希望重用RestTemplate某些API,则可以构建一个包装自定义实现RestTemplateRestTemplate API的适配器,而不会在代码中实际暴露RestTemplate API。

例如,您可以创建适配器接口,如下所示: –

 public interface MyRestTemplateAdapter { Object genericPost(String url); // same signature from RestTemplate that you want to reuse  T postForObject(String url, Object request, Class responseType, Object... uriVariables); } 

具体的自定义rest模板看起来像这样: –

 public class MyRestTemplateAdapterImpl implements MyRestTemplateAdapter { @Autowired private RestTemplate restTemplate; @Autowired private Jaxb2Marshaller jaxb2Marshaller; public Object genericPost(String url) { // code from above } public  T postForObject(String url, Object request, Class responseType, Object... uriVariables) { return restTemplate.postForObject(url, request, responseType); } } 

我仍然认为这种方法比RestTemplateRestTemplate并且您可以更好地控制如何处理Web服务调用的结果。

在尝试解决同样的问题时,我找到了以下解决方案。

我正在使用RestTemplate的默认实例,并使用xjc生成文件。 调用的转换器是Jaxb2RootElementHttpMessageConverter。

事实certificate,如果输入类使用XmlRootElement批注进行批注,则转换器将返回“real”类型。 也就是说,该方法

 protected Object readFromSource(Class clazz, HttpHeaders headers, Source source) 

可能会返回一个不是clazz实例的Object,因为clazz中存在一个XmlRootElement注释。 在这种情况下,clazz仅用于创建一个unmarshaller,它将解组clazz。

以下技巧解决了问题:如果我们定义

 @XmlRootElement() @XmlSeeAlso({ Exception.class, Job.class }) public static abstract class XmlResponse {} 

并将XmlResponse.class传递给postForObject(…),而响应将是Exception或Job。

这有点像黑客,但它解决了postForObject方法无法返回多个对象类的问题。