Custom ObjectMapper Avec Jersey 2.2 et Jackson 2.1
je me bats avec une application de repos avec Grizzly, Jersey et Jackson, parce que Jersey ignore mon ObjectMapper personnalisé.
POM dépendances:
<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 explicite de l'enregistrement de Jersey composants):
java prettyprint-override">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 for 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 L'un ni l'autre ObjectMapperResolver
constructeur ni getContext
se faire appeler. Que suis-je manquant? Je préfère utiliser Jersey 2.2 et Jackson 2.1, parce que 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
7 réponses
La solution suivante s'applique à la pile suivante (comme dans... c'est la configuration que j'ai utilisé pour tester)
Maillot 2.12, Jackson 2.4.x
j'ajoute mon message avec la solution que j'ai trouvée sur ce post car il était tout à fait pertinent pour les nombreuses recherches Google que j'ai mis dans aujourd'hui... C'est une solution lourde à ce que je considère comme un problème encore plus lourd.
1. Assurez-vous que votre configuration maven contient le 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>
2. Assurez-vous que votre configuration maven ne contient pas le jersey-media-json-jackson
dépendance:
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
</dependency>
3. Créer un @Provider
Elargissement du composant com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider
comme ceci:
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 voir, c'est aussi là que nous définissons l'instance personnalisée de com.fasterxml.jackson.databind.ObjectMapper
4. Étendre javax.ws.rs.core.Feature
par 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;
}
}
5. Vous devez enregistrer ce fournisseur personnalisé comme si, à condition de configurer votre application à l'aide de org.glassfish.jersey.server.ResourceConfig
comme ceci:
import org.glassfish.jersey.server.ResourceConfig;
...
public class MyApplication extends ResourceConfig {
public MyApplication() {
...
register(MarshallingFeature.class);
...
}
}
Autres notes et observations:
- Cette solution s'applique que vous utilisiez
javax.ws.rs.core.Response
pour envelopper les réponses de votre contrôleur ou non. - s'il Vous Plaît assurez-vous que vous prenez soigneusement en considération (copier/coller) les extraits de code suivants puisque les seuls" non-obligatoires " pour ainsi dire bits sont ceux concernant la configuration personnalisée de la
com.fasterxml.jackson.databind.ObjectMapper
.
@jcreason
Désolé pour déposer le ballon sur ce @jcreason, j'espère que vous êtes encore curieux. Donc j'ai vérifié le code de l'année dernière et c'est ce que j'ai trouvé pour fournir un mapper personnalisé.
le problème est que lors de l'initalisation de la fonctionnalité, tout mappeur d'objet personnalisé est désactivé par un code dans
org.glassfish.Jersey.Jackson.JacksonFeature: 77 (jersey-médias-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.interne.Jacksonautodiscutable
if (!context.getConfiguration().isRegistered(JacksonFeature.class)) {
context.register(JacksonFeature.class);
}
alors ce que j'ai fait était d'Enregistrer ma propre fonctionnalité qui enregistre mon propre fournisseur de mapper d'objet et tombe dans un fil de trip arrêt org.glassfish.Jersey.Jackson.JacksonFeature d'être enregistré et de passer outre mon objet cartographe...
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 custom object mapper...
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 trouvé une solution. J'ai eu pour instancier le Jackson Fournisseur par moi-même et de mettre mon custom ObjectMapper
. Un exemple pratique se trouve sur GitHub: https://github.com/svenwltr/example-grizzly-jersey-jackson/tree/stackoverflow-answer
j'ai supprimé mon ObjectMapperResolver
et modifié ma main
méthode:
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);
}
}
}
Merci de faire ceci:
1) Ajouter pom.dépendance xml
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.2</version>
</dependency>
2) enregistrer JacksonFeature dans la 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) Utiliser org.codehaus.Jackson.cartographie.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;
}
}
j'ai trouvé ça, basé sur un bricolage.
le problème semble être dans le mécanisme de détection automatique de la fonctionnalité de Jersey. Si vous comptez sur Jersey pour charger le JacksonJaxbJsonProvider, alors le fournisseur de contexte personnalisé pour votre ObjectMapper est ignoré. Si, à la place, vous enregistrez manuellement la fonctionnalité, cela fonctionne. Je pose l'hypothèse que cela a à voir avec le fournisseur autodétecté étant chargé dans un cadre de contexte différent, mais pour une solution, voici ce que j'ai fini avec. Note que j'ai enveloppé dans une fonction, vous devez être capable de l'enregistrer directement avec votre demande 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;
}
}
mise à jour novembre 2017: les choses ont un peu changé dans le monde de Jersey2. Si le ci-dessus ne fonctionne pas, essayez ceci:
La nouvelle méthode de 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;
}
}
@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 de 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>
dans le Jersey 2.17 docs: 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);
//...
}
}
Edit, oublié d'ajouter le JacksonObjectMapperProvider que vous fournissez dans register(..):
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, faire ce qui n'a pas fonctionné:
public class MyApplication extends ResourceConfig {
public MyApplication() {
register(MyObjectMapperProvider.class);
}}
MyObjectMapperProvider constructeur a été appelé, mais getContext () n'a jamais été appelé.
enregistrer MyObjectMapperProvider dans super () constructor make it work:
public class MyApplication extends ResourceConfig {
public MyApplication() {
super(
// register Jackson ObjectMapper resolver
MyObjectMapperProvider.class
);
}}