web-dev-qa-db-de.com

Benutzerdefinierter ObjectMapper mit Jersey 2.2 und Jackson 2.1

Ich habe Probleme mit einer REST Anwendung mit Grizzly, Jersey und Jackson, weil Jersey meinen benutzerdefinierten ObjectMapper ignoriert.

POM-Abhängigkeiten:

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

Resultierende Versionen sind: Grizzly 2.3.3, Jackson 2.1.4 und Jersey 2.2.

Hauptklasse (Ich möchte eine explizite Registrierung von Jersey-Komponenten):

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 für 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;
    }

}

Weder ObjectMapperResolver Konstruktor noch getContext werden aufgerufen. Was vermisse ich? Ich würde lieber Jersey 2.2 und Jackson 2.1 verwenden, weil es eine Abhängigkeit für eine andere Bibliothek ist.

Ein vollständiges Beispiel finden Sie auf GitHub: https://github.com/svenwltr/example-grizzly-jersey-jackson/tree/stackoverflow

44
svenwltr

Ich habe eine Lösung gefunden. Ich musste den Jackson Provider selbst instanziieren und mein benutzerdefiniertes ObjectMapper festlegen. Ein funktionierendes Beispiel finden Sie auf GitHub: https://github.com/svenwltr/example-grizzly-jersey-jackson/tree/stackoverflow-answer

Ich habe mein ObjectMapperResolver gelöscht und meine main -Methode geändert:

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);
        }
    }
}
29
svenwltr

Die folgende Lösung gilt für den folgenden Stack (wie in ... das ist das Setup, mit dem ich es getestet habe)

Jersey 2.12, Jackson 2.4.x

Ich füge meine Nachricht mit der Lösung hinzu, die ich für diesen Beitrag gefunden habe, da sie für die vielen Google-Suchen, die ich heute eingegeben habe, ziemlich relevant war noch umständlicheres Problem.

1. Stellen Sie sicher, dass Ihre Maven-Konfiguration die Abhängigkeit jackson-jaxrs-json-provider Enthält:

<dependency>
    <groupId>com.fasterxml.jackson.jaxrs</groupId>
    <artifactId>jackson-jaxrs-json-provider</artifactId>
    <version>2.4.1</version>
</dependency>

2. Vergewissern Sie sich, dass Ihre Maven-Konfiguration die Abhängigkeit jersey-media-json-jackson NICHT ENTHÄLT:

<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-jackson</artifactId>
</dependency>

3. Erstellen Sie eine @Provider - Komponente, die com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider Wie folgt erweitert:

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

Wie Sie sehen können, definieren wir hier auch die benutzerdefinierte Instanz von com.fasterxml.jackson.databind.ObjectMapper.

4. Erweitern Sie javax.ws.rs.core.Feature Über MarshallingFeature wie folgt:

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. Sie müssen diesen benutzerdefinierten Anbieter wie folgt registrieren, vorausgesetzt, Sie konfigurieren Ihre Anwendung über org.glassfish.jersey.server.ResourceConfig Wie folgt:

import org.glassfish.jersey.server.ResourceConfig;
...

public class MyApplication extends ResourceConfig {

    public MyApplication() {

        ...
        register(MarshallingFeature.class);
        ...
     }
 }

Sonstige Anmerkungen und Beobachtungen:

  1. Diese Lösung gilt unabhängig davon, ob Sie die Antworten Ihres Controllers mit javax.ws.rs.core.Response Abschließen oder nicht.
  2. Bitte achten Sie darauf, die folgenden Codeausschnitte sorgfältig zu berücksichtigen (kopieren/einfügen), da die einzigen sozusagen "nicht obligatorischen" Bits diejenigen sind, die sich auf die benutzerdefinierte Konfiguration von com.fasterxml.jackson.databind.ObjectMapper Beziehen.

@jcreason

Es tut mir leid, dass Sie den Ball auf diesen jcreason fallen lassen, ich hoffe, Sie sind immer noch neugierig. Also habe ich den Code aus dem letzten Jahr ausgecheckt und dies ist, was ich herausgefunden habe, um einen benutzerdefinierten Mapper bereitzustellen.

Das Problem bestand darin, dass während der Feature-Initialisierung alle benutzerdefinierten Objektzuordnungen durch einen Code in deaktiviert wurden

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

Diese Funktion wird jedoch nur von dieser Komponente registriert

org.glassfish.jersey.jackson.internal.JacksonAutoDiscoverable

if (!context.getConfiguration().isRegistered(JacksonFeature.class)) {
    context.register(JacksonFeature.class);
}

Also habe ich mein eigenes Feature registriert, das meinen eigenen Object-Mapper-Anbieter registriert und einen Trip-Wire löscht, wodurch org.glassfish.jersey.jackson.JacksonFeature daran gehindert wurde, registriert zu werden und meinen Object-Mapper zu überschreiben.

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

Und hier ist der benutzerdefinierte Objekt-Mapper-Anbieter ...

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);
    }
}
38
Filip

Ich habe das durch ein bisschen Basteln herausgefunden.

Das Problem liegt anscheinend im automatischen Erkennungsmechanismus von Jersey. Wenn Sie sich beim Laden des JacksonJaxbJsonProviders auf Jersey verlassen, wird der benutzerdefinierte Kontextanbieter für Ihren ObjectMapper ignoriert. Wenn Sie die Funktion stattdessen manuell registrieren, funktioniert sie. Ich nehme an, dass dies damit zu tun hat, dass der automatisch erkannte Anbieter in einen anderen Kontextbereich geladen wird. Als Lösung habe ich jedoch Folgendes gefunden. Beachten Sie, dass ich es in ein Feature eingebunden habe. Sie sollten es problemlos direkt in Ihrer Anwendung registrieren können.

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 November 2017: In der Jersey2-Welt hat sich einiges geändert. Wenn dies nicht funktioniert, versuchen Sie Folgendes:

Die neue Methode zur Bereitstellung Ihres eigenen ObjectMappers sieht nun folgendermaßen aus:

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

Bitte mach das:

1) füge pom.xml Abhängigkeit hinzu

<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-jackson</artifactId>
    <version>2.2</version>
</dependency>

2) registriere JacksonFeature in der 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) Verwenden Sie org.codehaus.jackson.map.ObjectMapper in Ihrer 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;
    }
}
8
alexey

Da ich einige Stunden gebraucht habe, um dies mit Java EE7 und Glassfish4) zum Laufen zu bringen, ist hier meine Lösung:

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

Die einzigen relevanten POM-Abhängigkeiten sind:

    <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>
7
lathspell

In den Jersey 2.17-Dokumenten: https://jersey.github.io/documentation/2.17/user-guide.html#jackson-registration

In der Bewerbung

@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);
    //...
  }
}

Bearbeiten, vergessen, den JacksonObjectMapperProvider hinzuzufügen, den Sie im Register (..) angeben:

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

Mit Jackson 2.7 hat das nicht funktioniert:

public class MyApplication extends ResourceConfig {
    public MyApplication() {
    register(MyObjectMapperProvider.class);
}}

Der Konstruktor MyObjectMapperProvider wurde aufgerufen, aber getContext () wurde nie aufgerufen.

Wenn Sie MyObjectMapperProvider im super () -Konstruktor registrieren, funktioniert Folgendes:

public class MyApplication extends ResourceConfig {
   public MyApplication() {
       super(
            // register Jackson ObjectMapper resolver
            MyObjectMapperProvider.class
       );
}}

Siehe dieser Jersey-Beispielcode .

2
c-toesca