如何在返回多种类型XML的URL上使用Spring RestTemplate和JAXB编组 [英] How to use Spring RestTemplate and JAXB marshalling on a URL that returns multiple types of XML

查看:83
本文介绍了如何在返回多种类型XML的URL上使用Spring RestTemplate和JAXB编组的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I need to make a Rest POST to a service that returns either a <job/> or an <exception/> and always status code 200. (lame 3rd party product!).

我的代码如下:

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

我的applicationContext.xml如下所示:

And my applicationContext.xml looks like:

<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
    <constructor-arg ref="httpClientFactory"/>

    <property name="messageConverters">
        <list>
            <bean class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
                <property name="marshaller" ref="jaxbMarshaller"/>
                <property name="unmarshaller" ref="jaxbMarshaller"/>
            </bean>
            <bean class="org.springframework.http.converter.FormHttpMessageConverter"/>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
        </list>
    </property>
</bean>

<bean id="jaxbMarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
    <property name="classesToBeBound">
        <list>
            <value>domain.fullspec.Job</value>
            <value>domain.fullspec.Exception</value>
        </list>
    </property>
</bean>

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

When I try to make this call and the service fails, I get:

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

在postForObject()调用中,我要求一个Job.class并且没有得到一个而且它正在变得烦恼。

In the postForObject() call, I am asking for a Job.class and not getting one and it is getting upset.

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

I am thinking I need to be able to do something along the lines of:

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

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

But this doesnt work because then JAXB complains that it doesnt know how to marshal to Object.class - not surprisingly.

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

I have attempted to create subclass of MarshallingHttpMessageConverter and override readFromSource()

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

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;
}

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

Unfortunately, this doesnt work because the underlying inputstream inside source has been closed by the time I retry it.

感激不尽的任何建议,

Tom

更新:我通过获取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 <exception> 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 ,我会将其粘贴到此处。

CopyInputStream is taken from http://www.velocityreviews.com/forums/t143479-how-to-make-a-copy-of-inputstream-object.html, i'll paste it here.

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());
}
}


推荐答案

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

@Tom: I don't think creating a custom MarshallingHttpMessageConverter will do you any good. The built-in converter is returning you the right class (Exception class) when the service fails, but it is the RestTemplate that doesn't know how to return Exception class to the callee because you have specified the response type as Job class.

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

I read the RestTemplate source code, and you are currently calling this API:-

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

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

As you can see, it returns type T based on your response type. What you probably need to do is to subclass RestTemplate and add a new postForObject() API that returns an Object instead of type T so that you can perform the instanceof check on the returned object.

UPDATE

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

I have been thinking about the solution for this problem, instead of using the built-in RestTemplate, why not write it yourself? I think that is better than subclassing RestTemplate to add a new method.

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

Here's my example... granted, I didn't test this code but it should give you an idea:-

// 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;
}

如果您希望重用某些来自<$的API的情况c $ c> RestTemplate ,您可以构建一个包装自定义实现的适配器并重用 RestTemplate API而不实际暴露 RestTemplate 遍布代码的API。

If there are circumstances where you want to reuse some of the APIs from RestTemplate, you can build an adapter that wraps your custom implementation and reuse RestTemplate APIs without actually exposing RestTemplate APIs all over your code.

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

For example, you can create an adapter interface, like this:-

public interface MyRestTemplateAdapter {
    Object genericPost(String url);

    // same signature from RestTemplate that you want to reuse
    <T> T postForObject(String url, Object request, Class<T> responseType, Object... uriVariables);
}

具体的自定义休息模板如下所示: -

The concrete custom rest template looks something like this:-

public class MyRestTemplateAdapterImpl implements MyRestTemplateAdapter {
    @Autowired
    private RestTemplate    restTemplate;

    @Autowired
    private Jaxb2Marshaller jaxb2Marshaller;

    public Object genericPost(String url) {
        // code from above
    }

    public <T> T postForObject(String url, Object request, Class<T> responseType, Object... uriVariables) {
        return restTemplate.postForObject(url, request, responseType);
    }
}

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

I still think this approach is much cleaner than subclassing RestTemplate and you have more control on how you want to handle the results from the web service calls.

这篇关于如何在返回多种类型XML的URL上使用Spring RestTemplate和JAXB编组的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆