Jersey ContextResolver GetContext() 只调用一次

Jersey ContextResolver GetContext() called only once

我有以下 ContextResolver<ObjectMapper> 实现,它基于查询参数应该 return 相应的 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() 函数在整个服务器生命周期内仅在第一次请求时调用一次。这个 class 的整个想法是在运行时根据 url 参数确定映射器是什么。

更新

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

更新2

嗯,似乎每个 class 都调用了 GetContext 一次,并为特定的 class 缓存了它。这就是为什么它需要一个 class 作为参数。所以看起来@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;
        }
    }

}

如果您根据要求需要它,则需要针对每次调用对其进行评估。我在这里建议的是将此逻辑移动到专用组件中并执行以下操作:

@GET
public Response demo(@Context final UriInfo uriInfoContext, final String requestBody) {
    final ObjectMapper objectMapper = objectMapperResolver.resolve(uriInfoContext.getQueryParameters());
    objectMapper.readValue(requestBody, MyClass.class);
    ...
}

其中objectMapperResolver封装了根据查询参数选择合适的ObjectMapper的逻辑

你应该考虑性能。 使用您的解决方案,您正在为每个请求创建一个新的 ObjectMapper 实例。这个好重!!!在 JProfile 测量期间,我发现 ObjectMapper 的创建是主要的性能障碍。

不确定是否只有 2 个用于漂亮/不漂亮的静态成员是关于线程安全的充分解决方案。您需要注意 JAX-RS 框架使用的机制来缓存 ObjectMapper,以免产生任何副作用。