Comment utiliser Spring RestTemplate et JAXB marshalling sur une URL qui renvoie plusieurs types de XML
J'ai besoin de faire un Repos POST pour un service qui renvoie une <job/>
ou un <exception/>
et toujours le code d'état 200
. (lame de la 3e partie du produit!).
J'ai un code comme:
Job job = getRestTemplate().postForObject(url, postData, Job.class);
Et mon applicationContext.xml ressemble:
<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>
Lorsque je tente de faire cet appel et le service échoue, j'obtiens:
Failed to convert value of type 'domain.fullspec.Exception' to required type 'domain.fullspec.Job'
Dans le postForObject (), je demande un Job.class et de ne pas en obtenir un, et c'est de s'énerver.
Je pense j'ai besoin d'être en mesure de faire quelque chose le long des lignes de:
Object o = getRestTemplate().postForObject(url, postData, Object.class);
if (o instanceof Job.class) {
...
else if (o instanceof Exception.class) {
}
Mais cela ne marche pas, car alors JAXB se plaint qu'il ne sait pas comment le maréchal de Object.class - il n'est pas surprenant.
J'ai tenté de créer la sous-classe de MarshallingHttpMessageConverter et remplacer readFromSource()
Objet protégé readFromSource(Classe clazz, HttpHeaders en-têtes, Source de 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;
}
Malheureusement, cela ne marche pas parce que le sous-jacent inputstream à l'intérieur de la source a été fermé le temps que j'ai réessayer il.
Toutes les suggestions reçues avec gratitude,
Tom
Mise à JOUR: j'ai eu que cela fonctionne en prenant une copie de l'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 est pris de http://www.velocityreviews.com/forums/t143479-how-to-make-a-copy-of-inputstream-object.htmlje vais la coller ici.
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());
}
}
source d'informationauteur Tom
Vous devez vous connecter pour publier un commentaire.
@Tom: je ne pense pas que la création d'une coutume MarshallingHttpMessageConverter va vous faire du bien. Le convertisseur intégré est de retour, vous avez le droit de classe (à l'Exception de la classe) lorsque le service échoue, mais c'est la
RestTemplate
qui ne sait pas comment revenir classe d'Exception pour le destinataire de l'appel parce que vous avez spécifié le type de réponse en tant que classe d'Emploi.J'ai lu le RestTemplate code sourceet que vous êtes actuellement à l'appel de cette API:-
Comme vous pouvez le voir, il renvoie le type de T en fonction de votre type de réponse. Ce que vous avez probablement besoin de faire est de la sous-classe
RestTemplate
et ajouter un nouveaupostForObject()
API qui renvoie un Objet de type T, de sorte que vous pouvez effectuer lesinstanceof
vérifier sur l'objet retourné.Mise à JOUR
J'ai pensé à la solution de ce problème, au lieu de l'aide de l'
RestTemplate
pourquoi ne pas écrire vous-même? Je pense que c'est mieux que de sous-classementRestTemplate
pour ajouter une nouvelle méthode.Voici mon exemple... certes, je n'ai pas testé ce code, mais il devrait vous donner une idée:-
Si il y a des circonstances où vous souhaitez réutiliser une partie de l'Api de
RestTemplate
vous pouvez construire un adaptateur qui enveloppe votre personnalisé de mise en œuvre et la réutilisationRestTemplate
Api sans exposerRestTemplate
Api partout dans votre code.Par exemple, vous pouvez créer une carte d'interface, comme ceci:-
Le béton personnalisé reste le modèle ressemble à quelque chose comme ceci:-
Je pense toujours que cette approche est beaucoup plus propre que le sous-classement
RestTemplate
et vous avez plus de contrôle sur la façon dont vous souhaitez gérer les résultats des appels de service web.Tout en essayant de résoudre le même problème, j'ai trouvé la solution suivante.
Je suis en utilisant l'une instance par défaut de RestTemplate, et les fichiers générés à l'aide de xjc. Le convertisseur qui est invoquée est Jaxb2RootElementHttpMessageConverter.
Il s'avère que le convertisseur retourne le type "réel" dans le cas où l'entrée de la classe est annotée avec XmlRootElement annotation. C'est la méthode
peut renvoyer un Objet qui n'est pas une instance de clazz, étant donné que clazz a un XmlRootElement annotation actuelle. Dans ce cas, clazz est seulement utilisé pour créer un unmarshaller qui unmarshall clazz.
L'astuce suivante permet de résoudre le problème: Si l'on définit
et passer XmlResponse.class pour postForObject(...), que la réponse va être d'Exception ou d'Emploi.
C'est un peu un hack, mais il résout le problème de postForObject méthode n'étant pas en mesure de retourner plus d'un objet de la classe.