java - Jersey ContextResolver GetContext() 仅调用一次

标签 java jersey jackson jax-rs

我有以下ContextResolver<ObjectMapper>实现,基于查询参数应该返回相应的 JSON 映射器(pretty/DateToUtc/Both):

import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;

@Provider
@Produces(MediaType.APPLICATION_JSON)
public class JsonMapper implements ContextResolver<ObjectMapper> {

    private ObjectMapper prettyPrintObjectMapper;
    private ObjectMapper dateToUtcMapper;
    private ObjectMapper bothMapper;
    private UriInfo uriInfoContext;

    public JsonMapper(@Context UriInfo uriInfoContext) throws Exception {
        this.uriInfoContext = uriInfoContext;

        this.prettyPrintObjectMapper = new ObjectMapper();
        this.prettyPrintObjectMapper.enable(SerializationFeature.INDENT_OUTPUT);

        this.dateToUtcMapper = new ObjectMapper();
        this.dateToUtcMapper.enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

        this.bothMapper = new ObjectMapper();
        this.bothMapper.enable(SerializationFeature.INDENT_OUTPUT);
        this.bothMapper.enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    }

    @Override
    public ObjectMapper getContext(Class<?> objectType) {
        System.out.println("hi");
        try {
            MultivaluedMap<String, String> queryParameters = uriInfoContext.getQueryParameters();
            Boolean containsPretty = queryParameters.containsKey("pretty");
            Boolean containsDate   = queryParameters.containsKey("date_to_utc");
            Boolean containsBoth   = containsPretty && containsDate;

            if (containsBoth) {
                System.out.println("Returning containsBoth");
                return bothMapper;
            }

            if (containsDate) {
                System.out.println("Returning containsDate");
                return dateToUtcMapper;
            }

            if (containsPretty) {
                System.out.println("Returning pretty");
                return prettyPrintObjectMapper;
            }

        } catch(Exception e) {
            // protect from invalid access to uriInfoContext.getQueryParameters()
        }

        System.out.println("Returning null");
        return null; // use default mapper
    }
}

以及以下主要应用程序:

 private Server configureServer() {
        ObjectMapper mapper = new ObjectMapper();

        ResourceConfig resourceConfig = new ResourceConfig();
        resourceConfig.packages(Calculator.class.getPackage().getName());
        resourceConfig.property(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true);
        // @ValidateOnExecution annotations on subclasses won't cause errors.
        resourceConfig.property(ServerProperties.BV_DISABLE_VALIDATE_ON_EXECUTABLE_OVERRIDE_CHECK, true);
        resourceConfig.register(JacksonFeature.class);
        resourceConfig.register(JsonMapper.class);
        resourceConfig.register(AuthFilter.class);
        ServletContainer servletContainer = new ServletContainer(resourceConfig);
        ServletHolder sh = new ServletHolder(servletContainer);
        Server server = new Server(serverPort);
        ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
        context.setContextPath("/");
        context.addServlet(sh, "/*");
        server.setHandler(context);
        return server;
    }

但是,getContext()函数在整个服务器生命周期中仅在第一次请求时调用一次。这个类的整体思想是在运行时根据 url 参数确定映射器是什么。

更新

getContext()对每个 uri 路径调用一次。例如,http://server/path1?pretty=true将为所有对/path1 的请求产生漂亮的输出,无论他们 future 漂亮 queryParam 。对 path2 的调用将再次调用 getContext,但不会调用以后的 path2 调用。

更新2

嗯,看起来像 GetContext为每个类调用一次,并为该特定类缓存它。这就是为什么它需要一个类作为参数。所以看来 @LouisF 是对的,并且 objectMapper 不适合条件序列化。然而,ContainerResponseFilter替代方案部分有效,但不公开 ObjectMapper 功能,例如将日期转换为 UTC。所以我现在很困惑什么是最合适的条件序列化解决方案。

已解决

在@LoisF的帮助下,我成功地使用ContainerResponseFilter进行条件序列化。 。我没用过ContextResolver 。下面是工作示例:

import java.io.IOException;

import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.Provider;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.jaxrs.cfg.EndpointConfigBase;
import com.fasterxml.jackson.jaxrs.cfg.ObjectWriterInjector;
import com.fasterxml.jackson.jaxrs.cfg.ObjectWriterModifier;

/**
 * Created by matt on 17/01/2016.
 */
@Provider
public class ResultTransformer implements ContainerResponseFilter {


    public static final String OUTPUT_FORMAT_HEADER = "X-Output-Format";
    public static final ObjectMapper MAPPER         = new ObjectMapper();

    public static class OutputFormat {
        Boolean pretty              = true;
        Boolean dateAsTimestamp     = false;

        public Boolean getPretty() {
            return pretty;
        }

        public void setPretty(Boolean pretty) {
            this.pretty = pretty;
        }

        @JsonProperty("date_as_timestamp")
        public Boolean getDateAsTimestamp() {
            return dateAsTimestamp;
        }

        public void setDateAsTimestamp(Boolean dateAsTimestamp) {
            this.dateAsTimestamp = dateAsTimestamp;
        }
    }

    @Override
    public void filter(ContainerRequestContext reqCtx, ContainerResponseContext respCtx) throws IOException {

        String outputFormatStr = reqCtx.getHeaderString(OUTPUT_FORMAT_HEADER);
        OutputFormat outputFormat;
        if (outputFormatStr == null) {
            outputFormat = new OutputFormat();
        } else {
            try {
                outputFormat = MAPPER.readValue(outputFormatStr, OutputFormat.class);
                ObjectWriterInjector.set(new IndentingModifier(outputFormat));
            } catch (Exception e) {
                e.printStackTrace();
                ObjectWriterInjector.set(new IndentingModifier(new OutputFormat()));
            }
        }
    }

    public static class IndentingModifier extends ObjectWriterModifier {

       private OutputFormat outputFormat;

        public IndentingModifier(OutputFormat outputFormat) {
            this.outputFormat = outputFormat;

        }


        @Override
        public ObjectWriter modify(EndpointConfigBase<?> endpointConfigBase, MultivaluedMap<String, Object> multivaluedMap, Object o, ObjectWriter objectWriter, JsonGenerator jsonGenerator) throws IOException {
            if(outputFormat.getPretty())      jsonGenerator.useDefaultPrettyPrinter();
            if (outputFormat.dateAsTimestamp)  {
                objectWriter = objectWriter.with(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
            } else {
                objectWriter = objectWriter.without(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
            }
            return objectWriter;
        }
    }

}

最佳答案

您应该考虑性能。 通过您的解决方案,您可以为每个请求创建一个新的 ObjectMapper 实例。这个还蛮重的!!!我发现 ObjectMapper 创建是 JProfile 测量期间主要的性能阻碍因素。

不确定仅拥有 2 个漂亮/非漂亮的静态成员是否足以解决线程安全问题。您需要注意 JAX-RS 框架使用的机制来缓存 ObjectMapper,以免产生任何副作用。

关于java - Jersey ContextResolver GetContext() 仅调用一次,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34838706/

相关文章:

java - jrebel 排除文件 ( messages.properties )

java - 当 ftp 站点未连接时 GUI 被卡住

java - 如何修复类 android.support.v7.internal.app.WindowDecorActionBar 的构建路径

java - Jackson JSON 映射键作为包含对象的属性

rest - 用Jackson(或Spring)解码Base64

java - Jackson @JsonCreator 无法识别的属性

java - RSA key 生成的 BigInteger 实现

java - 如何将服务器端 Jax-rs 调用与没有前缀的 native 文件混合?

Java Jersey 验证

jersey - 根据表单数据过滤 Jersey 请求