簡體   English   中英

帶有 Jersey 2.2 和 Jackson 2.1 的自定義 ObjectMapper

[英]Custom ObjectMapper with Jersey 2.2 and Jackson 2.1

我正在努力使用 Grizzly、Jersey 和 Jackson 的 REST 應用程序,因為 Jersey 忽略了我的自定義 ObjectMapper。

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>

結果版本是:Grizzly 2.3.3、Jackson 2.1.4 和 Jersey 2.2。

主類(我想顯式注冊 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);
        }
    }
}

ObjectMapper 的 ContextResolver:

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

}

ObjectMapperResolver構造函數和getContext都不會被調用。 我錯過了什么? 我更喜歡使用 Jersey 2.2 和 Jackson 2.1,因為它是另一個庫的依賴項。

完整示例可以在 GitHub 上找到: https : //github.com/svenwltr/example-grizzly-jersey-jackson/tree/stackoverflow

以下解決方案適用於以下堆棧(如...這是我用來測試它的設置)

澤西 2.12,傑克遜 2.4.x

我正在添加我的消息以及我在這篇文章中提出的解決方案,因為它與我今天進行的許多 Google 搜索非常相關......這是一個繁瑣的解決方案,我認為這是一個更麻煩的問題。

1. 確保您的 maven 配置包含jackson-jaxrs-json-provider依賴項:

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

2. 確保您的 maven 配置不包含jersey-media-json-jackson依賴項:

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

3. 創建一個擴展com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider@Provider組件, com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider所示:

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

正如您所觀察到的,這也是我們定義com.fasterxml.jackson.databind.ObjectMapper的自定義實例的com.fasterxml.jackson.databind.ObjectMapper

4. 通過MarshallingFeature擴展javax.ws.rs.core.Feature像這樣:

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. 您需要像這樣注冊這個自定義提供程序,前提是您通過org.glassfish.jersey.server.ResourceConfig像這樣配置您的應用程序:

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

public class MyApplication extends ResourceConfig {

    public MyApplication() {

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

其他注意事項和觀察:

  1. 無論您是否使用javax.ws.rs.core.Response來包裝控制器的響應,此解決方案都適用。
  2. 請確保您仔細考慮(復制/粘貼)以下代碼片段,因為唯一的“非強制性”可以說是關於com.fasterxml.jackson.databind.ObjectMapper的自定義配置的com.fasterxml.jackson.databind.ObjectMapper

@jcreason

很抱歉把球丟在這個@jcreason 上,我希望你仍然是好奇心。 所以我檢查了去年的代碼,這就是我提供自定義映射器的想法。

問題是在特性初始化期間,任何自定義對象映射器都會被某些代碼禁用

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

但是這個功能只會被這個組件注冊

org.glassfish.jersey.jackson.internal.JacksonAutoDiscoverable

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

所以我所做的是注冊我自己的功能,該功能注冊了我自己的對象映射器提供程序,並在絆線中阻止 org.glassfish.jersey.jackson.JacksonFeature 被注冊並覆蓋我的對象映射器......

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

這是自定義對象映射器提供程序...

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

我找到了解決方案。 我必須自己實例化 Jackson Provider 並設置我的自定義ObjectMapper 可以在 GitHub 上找到一個工作示例: https : //github.com/svenwltr/example-grizzly-jersey-jackson/tree/stackoverflow-answer

我刪除了我的ObjectMapperResolver並修改了我的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);
        }
    }
}

我想通了這一點,基於一點修補。

問題似乎出在 Jersey 的功能自動檢測機制中。 如果您依賴 Jersey 加載 JacksonJaxbJsonProvider,那么您的 ObjectMapper 的自定義上下文提供程序將被忽略。 相反,如果您手動注冊該功能,則它可以工作。 我假設這與將自動檢測到的提供程序加載到不同的上下文范圍有關,但至於解決方案,這就是我最終得到的。 請注意,我將它封裝到一個功能中,您應該可以毫無問題地直接在您的應用程序中注冊它。

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

2017 年 11 月更新:Jersey2 世界發生了一些變化。 如果上述方法不起作用,請嘗試以下操作:

提供您自己的 ObjectMapper 的新方法現在看起來像這樣:

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

請這樣做:

1) 添加 pom.xml 依賴

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

2) 在 Main.java 中注冊 JacksonFeature

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) 在你的資源中使用 org.codehaus.jackson.map.ObjectMapper

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

由於我花了幾個小時才能使用 Java EE7 和 Glassfish4,這是我的解決方案:

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

唯一相關的 POM 依賴項是:

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

來自 Jersey 2.17 文檔: https : //eclipse-ee4j.github.io/jersey.github.io/documentation/2.17/media.html#jackson-registration

在申請中

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

編輯,忘記添加您在 register(..) 中提供的 JacksonObjectMapperProvider:

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

使用 Jackson 2.7,這不起作用:

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

調用了 MyObjectMapperProvider 構造函數,但從未調用過 getContext()

在 super() 構造函數中注冊 MyObjectMapperProvider 使其工作:

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

請參閱此 Jersey 示例代碼

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM