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

前端之家收集整理的这篇文章主要介绍了java – 如何在返回多种类型的XML的URL上使用Spring RestTemplate和JAXB编组前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我需要对一个返回“< job />或者< exception />并始终处于状态码200.(跛脚第三方产品!).

我有如下代码

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

我的applicationContext.xml看起来像:

  1. <bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
  2. <constructor-arg ref="httpClientFactory"/>
  3.  
  4. <property name="messageConverters">
  5. <list>
  6. <bean class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
  7. <property name="marshaller" ref="jaxbMarshaller"/>
  8. <property name="unmarshaller" ref="jaxbMarshaller"/>
  9. </bean>
  10. <bean class="org.springframework.http.converter.FormHttpMessageConverter"/>
  11. <bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
  12. </list>
  13. </property>
  14. </bean>
  15.  
  16. <bean id="jaxbMarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
  17. <property name="classesToBeBound">
  18. <list>
  19. <value>domain.fullspec.Job</value>
  20. <value>domain.fullspec.Exception</value>
  21. </list>
  22. </property>
  23. </bean>

当我尝试使这个电话和服务失败,我得到:

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

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

我想我需要能够做的事情是:

  1. Object o = getRestTemplate().postForObject(url,Object.class);
  2. if (o instanceof Job.class) {
  3. ...
  4. else if (o instanceof Exception.class) {
  5. }

但是这不行,因为JAXB抱怨说它不知道如何组织到Object.class – 并不奇怪.

我试图创建MarshallingHttpMessageConverter的子类并重写readFromSource()

protected Object readFromSource(类clazz,HttpHeaders头,源代码){

  1. Object o = null;
  2. try {
  3. o = super.readFromSource(clazz,headers,source);
  4. } catch (Exception e) {
  5. try {
  6. o = super.readFromSource(MyCustomException.class,source);
  7. } catch (IOException e1) {
  8. log.info("Failed readFromSource "+e);
  9. }
  10. }
  11.  
  12. return o;
  13. }

不幸的是,这不工作,因为在我重试之前,源代码中的底层输入流已被关闭.

有意见的建议,

汤姆

更新:我已经通过获取一个inputStream的副本来工作

  1. protected Object readFromSource(Class<?> clazz,HttpHeaders headers,Source source) {
  2. InputStream is = ((StreamSource) source).getInputStream();
  3.  
  4. // Take a copy of the input stream so we can use it for initial JAXB conversion
  5. // and if that fails,we can try to convert to Exception
  6. CopyInputStream copyInputStream = new CopyInputStream(is);
  7.  
  8. // input stream in source is empty now,so reset using copy
  9. ((StreamSource) source).setInputStream(copyInputStream.getCopy());
  10.  
  11. Object o = null;
  12. try {
  13. o = super.readFromSource(clazz,source);
  14. // we have Failed to unmarshal to 'clazz' - assume it is <exception> and unmarshal to MyCustomException
  15.  
  16. } catch (Exception e) {
  17. try {
  18.  
  19. // reset input stream using copy
  20. ((StreamSource) source).setInputStream(copyInputStream.getCopy());
  21. o = super.readFromSource(MyCustomException.class,source);
  22.  
  23. } catch (IOException e1) {
  24. e1.printStackTrace();
  25. }
  26. e.printStackTrace();
  27. }
  28. return o;
  29.  
  30. }

CopyInputStream取自http://www.velocityreviews.com/forums/t143479-how-to-make-a-copy-of-inputstream-object.html,我将其粘贴到这里.

  1. import java.io.ByteArrayInputStream;
  2. import java.io.ByteArrayOutputStream;
  3. import java.io.IOException;
  4. import java.io.InputStream;
  5.  
  6. public class CopyInputStream
  7. {
  8. private InputStream _is;
  9. private ByteArrayOutputStream _copy = new ByteArrayOutputStream();
  10.  
  11. /**
  12. *
  13. */
  14. public CopyInputStream(InputStream is)
  15. {
  16. _is = is;
  17.  
  18. try
  19. {
  20. copy();
  21. }
  22. catch(IOException ex)
  23. {
  24. // do nothing
  25. }
  26. }
  27.  
  28. private int copy() throws IOException
  29. {
  30. int read = 0;
  31. int chunk = 0;
  32. byte[] data = new byte[256];
  33.  
  34. while(-1 != (chunk = _is.read(data)))
  35. {
  36. read += data.length;
  37. _copy.write(data,chunk);
  38. }
  39.  
  40. return read;
  41. }
  42.  
  43. public InputStream getCopy()
  44. {
  45. return (InputStream)new ByteArrayInputStream(_copy.toByteArray());
  46. }
  47. }

解决方法

@Tom:我不认为创建一个自定义的MarshallingHttpMessageConverter会给你带来任何好处.内置的转换器在服务失败时返回正确的类(Exception类),但是RestTemplate不知道如何将Exception类返回给被调用者,因为您已将响应类型指定为Job类.

我读了RestTemplate source code,你目前正在调用这个API:

  1. public <T> T postForObject(URI url,Object request,Class<T> responseType) throws RestClientException {
  2. HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request,responseType);
  3. HttpMessageConverterExtractor<T> responseExtractor =
  4. new HttpMessageConverterExtractor<T>(responseType,getMessageConverters());
  5. return execute(url,HttpMethod.POST,requestCallback,responseExtractor);
  6. }

您可以看到,它会根据您的响应类型返回类型T.您可能需要做的是对RestTemplate进行子类化,并添加一个返回Object而不是类型T的新的PostForObject()API,以便您可以对返回的对象执行instanceof检查.

UPDATE

我一直在想这个问题的解决方案,而不是使用内置的RestTemplate,为什么不自己写?我认为比子类RestTemplate更好的添加一个新的方法.

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

  1. // reuse the same marshaller wired in RestTemplate
  2. @Autowired
  3. private Jaxb2Marshaller jaxb2Marshaller;
  4.  
  5. public Object genericPost(String url) {
  6. // using Commons HttpClient
  7. HttpClient client = new HttpClient();
  8. PostMethod method = new PostMethod(url);
  9.  
  10. // add your data here
  11. method.addParameter("data","your-data");
  12.  
  13. try {
  14. int returnCode = client.executeMethod(method);
  15.  
  16. // status code is 200
  17. if (returnCode == HttpStatus.SC_OK) {
  18. // using Commons IO to convert inputstream to string
  19. String xml = IoUtil.toString(method.getResponseBodyAsStream());
  20. return jaxb2Marshaller.unmarshal(new StreamSource(new ByteArrayInputStream(xml.getBytes("UTF-8"))));
  21. }
  22. else {
  23. // handle error
  24. }
  25. }
  26. catch (Exception e) {
  27. throw new RuntimeException(e);
  28. }
  29. finally {
  30. method.releaseConnection();
  31. }
  32.  
  33. return null;
  34. }

如果有些情况需要重新使用RestTemplate中的一些API,那么您可以构建一个适配器来封装您的自定义实现并重用RestTemplate API,而不会在您的代码中实际暴露RestTemplate API.

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

  1. public interface MyRestTemplateAdapter {
  2. Object genericPost(String url);
  3.  
  4. // same signature from RestTemplate that you want to reuse
  5. <T> T postForObject(String url,Class<T> responseType,Object... uriVariables);
  6. }

具体的定制休息模板看起来像这样:

  1. public class MyRestTemplateAdapterImpl implements MyRestTemplateAdapter {
  2. @Autowired
  3. private RestTemplate restTemplate;
  4.  
  5. @Autowired
  6. private Jaxb2Marshaller jaxb2Marshaller;
  7.  
  8. public Object genericPost(String url) {
  9. // code from above
  10. }
  11.  
  12. public <T> T postForObject(String url,Object... uriVariables) {
  13. return restTemplate.postForObject(url,request,responseType);
  14. }
  15. }

我仍然认为这种方法比使用RestTemplate的子类更清晰,您可以更好地控制如何处理Web服务调用的结果.

猜你在找的Java相关文章