En commençant avec Jersey, j'ai essayé de reproduire l'exemple simple dans la dernière documentation de Jersey ' construction de réponses '. Pour autant que je sache, cette partie devrait montrer comment Response
et ResponseBuilder
peuvent être utilisés pour renvoyer facilement une réponse en combinaison avec Entity<T>
pour le contenu de la réponse.
Maintenant, la documentation indique que plusieurs types de données sont pris en charge par défaut (ici: ' Représentations et Java types '). String
prime parmi eux, correspondant à tout type de média.
De toutes les variantes que j'ai essayées, la suivante est la plus simple:
@POST
public Response post() {
URI createdUri;
try {
createdUri = new URI("http://test.lan");
} catch (final URISyntaxException e) {
throw new WebApplicationException(e);
}
return Response.created(createdUri).entity(Entity.text("someContent")).build();
}
J'ai toujours eu la même erreur (trace de pile complète ci-dessous) lors de l'appel de la demande: org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException: MessageBodyWriter not found for media type=text/plain, type=class javax.ws.rs.client.Entity, genericType=class javax.ws.rs.client.Entity.
Je pense que cela signifie qu'un fournisseur approprié n'a pas été trouvé pour ce type générique d'entité. Cependant, String doit être pris en charge OOTB?
J'ai trouvé que StringMessageProvider
est probablement l'implémentation Jersey 1 de ce fournisseur, et les classes connexes les plus proches que j'ai trouvées dans mes bibliothèques Jersey 2 sont les classes dans org.glassfish.jersey.message.internal
en jersey-commun. Parmi les nombreux fournisseurs, il y a le StringMessageProvider
, qui me semble être un fournisseur potentiel pour cela.
J'ai recherché le problème, et bien qu'il y ait beaucoup de gens qui l'obtiennent en essayant incorrectement d'utiliser un fournisseur personnalisé, je n'ai rien trouvé sur les fournisseurs par défaut OOTB ne fonctionne pas ..
J'ai vérifié mes bibliothèques, et en ce moment, j'ai les dépendances suivantes dans mon pom (entre autres):
J'ai regardé en ligne mais cela semble être tout ce dont j'ai besoin, bien que je n'ai pas avec certitude trouvé les bonnes classes de fournisseur pour String et JAXB/JSON dans les bocaux.
POST HTTP/1.1
User-Agent: Fiddler
Host: 127.0.0.1
Content-Length: 0
Et encore une fois essayé plusieurs variantes.
06-Jan-2015 21:13:54 org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor aroundWriteTo
SEVERE: MessageBodyWriter not found for media type=text/plain, type=class javax.ws.rs.client.Entity, genericType=class javax.ws.rs.client.Entity.
06-Jan-2015 21:13:54 org.Apache.catalina.core.StandardWrapperValve invoke
SEVERE: Servlet.service() for servlet TestService threw exception
org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException: MessageBodyWriter not found for media type=text/plain, type=class javax.ws.rs.client.Entity, genericType=class javax.ws.rs.client.Entity.
at org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.aroundWriteTo(WriterInterceptorExecutor.Java:247)
at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.Java:162)
at org.glassfish.jersey.server.internal.JsonWithPaddingInterceptor.aroundWriteTo(JsonWithPaddingInterceptor.Java:103)
at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.Java:162)
at org.glassfish.jersey.server.internal.MappableExceptionWrapperInterceptor.aroundWriteTo(MappableExceptionWrapperInterceptor.Java:88)
at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.Java:162)
at org.glassfish.jersey.message.internal.MessageBodyFactory.writeTo(MessageBodyFactory.Java:1154)
at org.glassfish.jersey.server.ServerRuntime$Responder.writeResponse(ServerRuntime.Java:571)
at org.glassfish.jersey.server.ServerRuntime$Responder.processResponse(ServerRuntime.Java:378)
at org.glassfish.jersey.server.ServerRuntime$Responder.process(ServerRuntime.Java:368)
at org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.Java:262)
at org.glassfish.jersey.internal.Errors$1.call(Errors.Java:271)
at org.glassfish.jersey.internal.Errors$1.call(Errors.Java:267)
at org.glassfish.jersey.internal.Errors.process(Errors.Java:315)
at org.glassfish.jersey.internal.Errors.process(Errors.Java:297)
at org.glassfish.jersey.internal.Errors.process(Errors.Java:267)
at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.Java:319)
at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.Java:236)
at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.Java:1028)
at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.Java:373)
at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.Java:381)
at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.Java:344)
at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.Java:219)
at org.Apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.Java:290)
at org.Apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.Java:206)
at org.Apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.Java:233)
at org.Apache.catalina.core.StandardContextValve.invoke(StandardContextValve.Java:191)
at org.Apache.catalina.core.StandardHostValve.invoke(StandardHostValve.Java:127)
at org.Apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.Java:102)
at org.Apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.Java:109)
at org.Apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.Java:293)
at org.Apache.coyote.http11.Http11Processor.process(Http11Processor.Java:859)
at org.Apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.Java:602)
at org.Apache.Tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.Java:489)
at Java.lang.Thread.run(Thread.Java:662)
La même erreur (pour application/json) se produit maintenant que j'ai annoté une classe avec @XmlRootElement
et essayez de le renvoyer dans une méthode conforme à la documentation de Jersey:
@GET
@Produces(MediaType.APPLICATION_JSON)
public Foo sampleFoo() {
Foo foo = new Foo();
return foo;
}
Où Foo
est annoté avec @XmlRootElement
.
J'ai également ajouté jersey-media-json-jackson en tant que dépendance, que je peux voir contient un fournisseur JSONJaxb explicite. Cependant, cela ne semble pas être repris d'une manière ou d'une autre.
javax.ws.rs.client.Entity
est une classe côté client. La spécification JAX-RS ne dit rien de son utilisation côté serveur. Mais je peux confirmer avec de nombreux tests différents, que le résultat sera similaire à ce que vous voyez (avec Jersey au moins). Avec Resteasy, il enverra simplement la Entity.toString()
Comme cela ne fonctionne pas pour Resteasy ou Jersey, je ne dirai pas que c'est un bogue, mais peut-être une erreur dans la documentation de Jersey, qui en a donné un exemple d'utilisation:
@POST
@Consumes("application/xml")
public Response post(String content) {
URI createdUri = ...
String createdContent = create(content);
return Response.created(createdUri)
.entity(Entity.text(createdContent)).build();
}
Ce qui précède a également échoué pour moi. Mais vous n'avez pas tort de dire
... plusieurs types de données sont pris en charge par défaut
comme ils sont. Pour que votre exemple fonctionne, changez simplement la Entity.text("someContent")
en simplement "someContent"
return Response.created(createdUri).entity("someContent").build();
Et juste pour être complet, tilisation côté client pourrait ressembler à quelque chose comme
Response response = webTarget.request().post(Entity.text("Hello World"));
qui fonctionne très bien.
Jusqu'à (je crois) Jersey 2.9, le module jersey-media-json-jackson
N'est pas configuré automatiquement. Donc, avec 2.6, nous devons installer la configuration soit par l'analyse des packages dans la web.xml
Ou dans la sous-classe Application
. Dans les deux cas, un web.xml
est requis . Comme indiqué ici en ce qui concerne un environnement de servlet 2.x, qui est Tomcat 6.
Dans l'environnement Servlet 2.5, vous devez déclarer explicitement le conteneur Jersey Servlet dans le fichier descripteur de déploiement web.xml de votre application Web.
Donc, pour rechercher les classes de fournisseur JSON, vous devez spécifier le package dans le paramètre d'initiation jersey.config.server.provider.packages
. Un exemple web.xml serait quelque chose comme ça
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://Java.Sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://Java.Sun.com/xml/ns/javaee
http://Java.Sun.com/xml/ns/javaee/web-app_2_5.xsd">
<servlet>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-class>
org.glassfish.jersey.servlet.ServletContainer
</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>
thepackage.of.your.resources,
org.codehaus.jackson.jaxrs <!-- Jackson providers -->
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Jersey Web Application</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
</web-app>
Vous êtes également autorisé à utiliser une sous-classe Application
(à partir de laquelle ResourceConfig
s'étend). Il suffit de le spécifier dans le web.xml
. Un exemple de configuration pourrait être quelque chose comme
public class MyApplication extends ResourceConfig {
public MyApplication() {
register(JacksonFeature.class);
packages("thepackage.of.your.resources");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://Java.Sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://Java.Sun.com/xml/ns/javaee
http://Java.Sun.com/xml/ns/javaee/web-app_2_5.xsd">
<servlet>
<servlet-name>MyApplication</servlet-name>
<servlet-class>
org.glassfish.jersey.servlet.ServletContainer
</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>jersey2.Tomcat6.MyApplication</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>MyApplication</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
</web-app>
Remarque: Tout cela a été testé par rapport à votre même environnement, en plus d'utiliser Eclipse. J'utilise Netbeans, mais cela ne devrait faire aucune différence. De plus, les seules dépendances Maven dont j'avais besoin étaient
<dependencies>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet-core</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>${jersey.version}</version>
</dependency>
</dependencies>
<jersey.version>2.6</jersey.version>
Sur une autre note, pour simplifier le développement, je viens de créer un simple archétype Maven avec les coordonnées suivantes
GroupId: org.glassfish.jersey.archetypes
ArtifactId: jersey-quickstart-webapp
Version: 2.6
Vous pouvez également voir Création d'un nouveau projet à partir de l'archétype Maven
Dans mon cas, j'ai ajouté la dépendance jersey-media-json-jackson. Ça a marché pour moi.
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.20</version>
</dependency>
En ce qui concerne le format texte/simple, cela fonctionne-t-il?
return Response.created(createdUri).type(MediaType.TEXT_PLAIN).entity("someContent").build();
Pour la sortie JSON, j'ai ces dépendances
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
ainsi qu'une implémentation jaxb. Tout fera l'affaire, j'utilise
<dependency>
<groupId>com.Sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
</dependency>
Je définis également un fournisseur de mappeur d'objets, bien que je ne sois pas sûr à 100% qu'il soit requis (sauf si vous souhaitez personnaliser):
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector;
@Provider
public class ObjectMapperProvider implements ContextResolver<ObjectMapper> {
ObjectMapper mapper;
public ObjectMapperProvider() {
mapper = new ObjectMapper();
mapper.configure( SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false );
mapper.configure( SerializationFeature.INDENT_OUTPUT, true );
mapper.configure( SerializationFeature.WRITE_NULL_MAP_VALUES, true );
mapper.configure( SerializationFeature.WRITE_EMPTY_JSON_ARRAYS, true );
mapper.setAnnotationIntrospector( new JaxbAnnotationIntrospector(TypeFactory.defaultInstance()) );
}
@Override
public ObjectMapper getContext(Class<?> type) {
return mapper;
}
}
De plus, je pense que vous devez enregistrer la fonctionnalité Jackson:
@ApplicationPath("")
public class Application extends ResourceConfig {
public Application() {
register( JacksonFeature.class );
}
}
Je dois noter que tout est configuré à l'aide de Jersey 2.11.