Je me bats avec une application REST avec Grizzly, Jersey et Jackson), car Jersey ignore mon ObjectMapper personnalisé.
Dépendances POM:
<dependencies>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-grizzly2-servlet</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>2.1.4</version>
</dependency>
</dependencies>
Les versions résultantes sont: Grizzly 2.3.3, Jackson 2.1.4 et Jersey 2.2.
Classe principale (je veux un enregistrement explicite des composants de Jersey):
public class Main {
public static void main(String[] args) {
try {
ResourceConfig rc = new ResourceConfig();
rc.register(ExampleResource.class);
rc.register(ObjectMapperResolver.class);
HttpHandler handler = ContainerFactory.createContainer(
GrizzlyHttpContainer.class, rc);
URI uri = new URI("http://0.0.0.0:8080/");
HttpServer server = GrizzlyHttpServerFactory.createHttpServer(uri);
ServerConfiguration config = server.getServerConfiguration();
config.addHttpHandler(handler, "/");
server.start();
System.in.read();
} catch (ProcessingException | URISyntaxException | IOException e) {
throw new Error("Unable to create HTTP server.", e);
}
}
}
ContextResolver pour ObjectMapper:
@Provider
@Produces(MediaType.APPLICATION_JSON)
public class ObjectMapperResolver implements ContextResolver<ObjectMapper> {
private final ObjectMapper mapper;
public ObjectMapperResolver() {
System.out.println("new ObjectMapperResolver()");
mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
}
@Override
public ObjectMapper getContext(Class<?> type) {
System.out.println("ObjectMapperResolver.getContext(...)");
return mapper;
}
}
Ni ObjectMapperResolver
constructeur ni getContext
ne sont appelés. Qu'est-ce que je rate? Je préférerais utiliser Jersey 2.2 et Jackson 2.1, car c'est une dépendance pour une autre lib.
Un exemple complet peut être trouvé sur GitHub: https://github.com/svenwltr/example-grizzly-jersey-jackson/tree/stackoverflow
J'ai trouvé une solution. Je devais instancier moi-même le fournisseur Jackson et définir ma coutume ObjectMapper
. Un exemple de travail peut être trouvé sur GitHub: https://github.com/svenwltr/example-grizzly-jersey-jackson/tree/stackoverflow-answer
J'ai supprimé mon ObjectMapperResolver
et modifié ma méthode main
:
public class Main {
public static void main(String[] args) {
try {
// create custom ObjectMapper
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
// create JsonProvider to provide custom ObjectMapper
JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider();
provider.setMapper(mapper);
// configure REST service
ResourceConfig rc = new ResourceConfig();
rc.register(ExampleResource.class);
rc.register(provider);
// create Grizzly instance and add handler
HttpHandler handler = ContainerFactory.createContainer(
GrizzlyHttpContainer.class, rc);
URI uri = new URI("http://0.0.0.0:8080/");
HttpServer server = GrizzlyHttpServerFactory.createHttpServer(uri);
ServerConfiguration config = server.getServerConfiguration();
config.addHttpHandler(handler, "/");
// start
server.start();
System.in.read();
} catch (ProcessingException | URISyntaxException | IOException e) {
throw new Error("Unable to create HTTP server.", e);
}
}
}
La solution suivante s'applique à la pile suivante (comme dans ... c'est la configuration que j'ai utilisée pour la tester)
J'ajoute mon message avec la solution que j'ai trouvée dans ce billet, car il était tout à fait pertinent pour les nombreuses recherches Google que j'ai effectuées aujourd'hui ... C'est une solution encombrante à ce que je crois être problème encore plus encombrant.
jackson-jaxrs-json-provider
dépendance:<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>2.4.1</version>
</dependency>
jersey-media-json-jackson
dépendance:<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
</dependency>
@Provider
composant s'étendant com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider
ainsi:import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.Provider;
@Provider
@Produces(MediaType.APPLICATION_JSON)
public class CustomJsonProvider extends JacksonJaxbJsonProvider {
private static ObjectMapper mapper = new ObjectMapper();
static {
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.enable(SerializationFeature.INDENT_OUTPUT);
}
public CustomJsonProvider() {
super();
setMapper(mapper);
}
}
Comme vous pouvez le constater, nous définissons également l’instance personnalisée de com.fasterxml.jackson.databind.ObjectMapper
javax.ws.rs.core.Feature
via MarshallingFeature
comme ceci:import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
public class MarshallingFeature implements Feature {
@Override
public boolean configure(FeatureContext context) {
context.register(CustomJsonProvider.class, MessageBodyReader.class, MessageBodyWriter.class);
return true;
}
}
org.glassfish.jersey.server.ResourceConfig
ainsi:import org.glassfish.jersey.server.ResourceConfig;
...
public class MyApplication extends ResourceConfig {
public MyApplication() {
...
register(MarshallingFeature.class);
...
}
}
Autres notes et observations:
javax.ws.rs.core.Response
pour envelopper ou non les réponses de votre contrôleur.com.fasterxml.jackson.databind.ObjectMapper
.Désolé de laisser tomber la balle sur celle-ci @ jcreason, j'espère que vous êtes toujours curieux. J'ai donc vérifié le code de l'année dernière et c'est ce que j'ai proposé de fournir un mappeur personnalisé.
Le problème était que, pendant l’initialisation de la fonctionnalité, tous les mappeurs d’objets personnalisés étaient désactivés par un code dans
org.glassfish.jersey.jackson.JacksonFeature: 77 (jersey-media-json-jackson-2.12.jar)
// Disable other JSON providers.
context.property(PropertiesHelper.getPropertyNameForRuntime(InternalProperties.JSON_FEATURE, config.getRuntimeType()), JSON_FEATURE);
Mais cette fonctionnalité n'est enregistrée que par ce composant
org.glassfish.jersey.jackson.internal.JacksonAutoDiscoverable
if (!context.getConfiguration().isRegistered(JacksonFeature.class)) {
context.register(JacksonFeature.class);
}
Donc, ce que j'ai fait était d'enregistrer ma propre fonctionnalité qui enregistre mon propre fournisseur de mappeur d'objet et tombe dans un fil de déclenchement empêchant org.glassfish.jersey.jackson.JacksonFeature d'être enregistré et d'ignorer mon mappeur d'objet ...
import com.fasterxml.jackson.jaxrs.base.JsonMappingExceptionMapper;
import com.fasterxml.jackson.jaxrs.base.JsonParseExceptionMapper;
import org.glassfish.jersey.internal.InternalProperties;
import org.glassfish.jersey.internal.util.PropertiesHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.core.Configuration;
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
public class MarshallingFeature implements Feature {
private final static String JSON_FEATURE = MarshallingFeature.class.getSimpleName();
@Override
public boolean configure(FeatureContext context) {
context.register(JsonParseExceptionMapper.class);
context.register(JsonMappingExceptionMapper.class);
context.register(JacksonJsonProviderAtRest.class, MessageBodyReader.class, MessageBodyWriter.class);
final Configuration config = context.getConfiguration();
// Disables discoverability of org.glassfish.jersey.jackson.JacksonFeature
context.property(
PropertiesHelper.getPropertyNameForRuntime(InternalProperties.JSON_FEATURE,
config.getRuntimeType()), JSON_FEATURE);
return true;
}
}
Et voici le fournisseur de mappeur d'objet personnalisé ...
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.Provider;
@Provider
@Produces(MediaType.APPLICATION_JSON)
public class JacksonJsonProviderAtRest extends JacksonJaxbJsonProvider {
private static ObjectMapper objectMapperAtRest = new ObjectMapper();
static {
objectMapperAtRest.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapperAtRest.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapperAtRest.configure(SerializationFeature.INDENT_OUTPUT, true); // Different from default so you can test it :)
objectMapperAtRest.setSerializationInclusion(JsonInclude.Include.ALWAYS);
}
public JacksonJsonProviderAtRest() {
super();
setMapper(objectMapperAtRest);
}
}
J'ai compris cela en me basant sur un peu de bricolage.
Le problème semble être lié au mécanisme de détection automatique des fonctionnalités de Jersey. Si vous comptez sur Jersey pour charger JacksonJaxbJsonProvider, le fournisseur de contexte personnalisé de votre ObjectMapper est ignoré. Si, à la place, vous enregistrez manuellement la fonctionnalité, cela fonctionne. Je suppose que cela a à voir avec le fournisseur détecté automatiquement chargé dans un contexte différent, mais en ce qui concerne une solution, voici ce que j'ai obtenu. Notez que je l'ai intégré dans une fonctionnalité, vous devriez pouvoir l'enregistrer directement avec votre application sans problème.
public final class RequestMappingFeature implements Feature {
@Override
public boolean configure(final FeatureContext context) {
context.register(ObjectMapperProvider.class);
// If you comment out this line, it stops working.
context.register(JacksonJaxbJsonProvider.class);
return true;
}
}
PDATE Novembre 2017: Les choses ont un peu changé dans le monde Jersey2. Si ce qui précède ne fonctionne pas, essayez ceci:
La nouvelle méthode pour fournir votre propre ObjectMapper ressemble maintenant à ceci:
public final class JacksonFeature implements Feature {
private static final ObjectMapper MAPPER;
static {
// Create the new object mapper.
MAPPER = new ObjectMapper();
// Enable/disable various configuration flags.
MAPPER.configure(
DeserializationFeature.READ_ENUMS_USING_TO_STRING, true);
// ... Add your own configurations here.
}
@Override
public boolean configure(final FeatureContext context) {
JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider(
MAPPER, DEFAULT_ANNOTATIONS);
context.register(provider);
return true;
}
}
Fais-le s'il te plaît:
1) ajouter une dépendance pom.xml
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.2</version>
</dependency>
2) inscrire JacksonFeature dans le Main.Java
public class Main {
public static void main(String[] args) {
try {
ResourceConfig rc = new ResourceConfig();
rc.register(ExampleResource.class);
rc.register(ObjectMapperResolver.class);
rc.register(JacksonFeature.class);
HttpHandler handler = ContainerFactory.createContainer(
GrizzlyHttpContainer.class, rc);
URI uri = new URI("http://0.0.0.0:8080/");
HttpServer server = GrizzlyHttpServerFactory.createHttpServer(uri);
ServerConfiguration config = server.getServerConfiguration();
config.addHttpHandler(handler, "/");
server.start();
System.in.read();
} catch (ProcessingException | URISyntaxException | IOException e) {
throw new Error("Unable to create HTTP server.", e);
}
}
}
3) Utilisez org.codehaus.jackson.map.ObjectMapper dans votre ressource
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig.Feature;
@Provider
@Produces(MediaType.APPLICATION_JSON)
public class ObjectMapperResolver implements ContextResolver<ObjectMapper> {
private final ObjectMapper mapper;
public ObjectMapperResolver() {
System.out.println("new ObjectMapperResolver()");
mapper = new ObjectMapper();
mapper.enable(Feature.INDENT_OUTPUT);
}
@Override
public ObjectMapper getContext(Class<?> type) {
System.out.println("ObjectMapperResolver.getContext(...)");
return mapper;
}
}
Comme cela m'a pris quelques heures pour que cela fonctionne avec Java EE7 et Glassfish4, voici ma solution:
@javax.ws.rs.ApplicationPath("withJackson")
public class ApplicationConfig extends Application {
private static final Logger log = Java.util.logging.Logger.getLogger(ApplicationConfig.class.getName());
@Override
public Set<Object> getSingletons() {
Set<Object> set = new HashSet<>();
log.log(Level.INFO, "Enabling custom Jackson JSON provider");
set.add(new JacksonJsonProvider().configure(SerializationFeature.INDENT_OUTPUT, true));
return set;
}
@Override
public Map<String, Object> getProperties() {
Map<String, Object> map = new HashMap<>();
log.log(Level.INFO, "Disabling MOXy JSON provider");
map.put("jersey.config.disableMoxyJson.server", true);
return map;
}
@Override
public Set<Class<?>> getClasses() {
Set<Class<?>> resources = new Java.util.HashSet<>();
addRestResourceClasses(resources);
return resources;
}
/**
* Do not modify addRestResourceClasses() method.
* It is automatically populated with
* all resources defined in the project.
* If required, comment out calling this method in getClasses().
*/
private void addRestResourceClasses(Set<Class<?>> resources) {
resources.add(com.fasterxml.jackson.jaxrs.base.JsonMappingExceptionMapper.class);
resources.add(com.fasterxml.jackson.jaxrs.base.JsonParseExceptionMapper.class);
resources.add(com.fasterxml.jackson.jaxrs.json.JsonMappingExceptionMapper.class);
resources.add(com.fasterxml.jackson.jaxrs.json.JsonParseExceptionMapper.class);
resources.add(de.lathspell.Java_test_ee7_json.Api.class);
resources.add(de.lathspell.Java_test_ee7_json.with_jackson.MyExceptionMapper.class);
}
Les seules dépendances POM pertinentes sont:
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>2.2.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.2.3</version>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>7.0</version>
<scope>provided</scope>
</dependency>
D'après les documents Jersey 2.17: https://jersey.github.io/documentation/2.17/user-guide.html#jackson-registration
Dans l'application
@ApplicationPath("/")
public class MyApplication extends ResourceConfig {
public MyApplication() {
register(JacksonFeature.class);
// This is the class that you supply, Call it what you want
register(JacksonObjectMapperProvider.class);
//...
}
}
Modifier, j'ai oublié d'ajouter le JacksonObjectMapperProvider que vous fournissez dans le registre (..):
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
@Provider
public class JacksonObjectMapperProvider implements ContextResolver<ObjectMapper>{
final ObjectMapper defaultObjectMapper;
public JacksonObjectMapperProvider() {
defaultObjectMapper = createDefaultMapper();
}
@Override
public ObjectMapper getContext(Class<?> type) {return defaultObjectMapper;}
public static ObjectMapper createDefaultMapper() {
final ObjectMapper jackson = new ObjectMapper();
// any changes to the ObjectMapper is up to you. Do what you like.
// The ParameterNamesModule is optional,
// it enables you to have immutable POJOs in Java8
jackson.registerModule(new ParameterNamesModule());
jackson.enable(SerializationFeature.INDENT_OUTPUT);
jackson.disable(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS);
jackson.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
return jackson;
}
}
Avec Jackson 2.7, cela n’a pas fonctionné:
public class MyApplication extends ResourceConfig {
public MyApplication() {
register(MyObjectMapperProvider.class);
}}
Le constructeur MyObjectMapperProvider a été appelé, mais getContext () n'a jamais été appelé .
L'enregistrement de MyObjectMapperProvider dans le constructeur super () le rend efficace:
public class MyApplication extends ResourceConfig {
public MyApplication() {
super(
// register Jackson ObjectMapper resolver
MyObjectMapperProvider.class
);
}}
Voir cet exemple de code Jersey .