Jersey ContextResolver GetContext() 只调用一次



我有以下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 参数确定映射器是什么。

更新

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

UPDATE2

好吧,似乎为每个类调用一次GetContext,并为该特定类缓存它。这就是为什么它需要一个类作为参数。所以看起来@LouisF是对的,对象映射器不适合条件序列化。但是,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,以免产生任何副作用。

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

@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 的逻辑

相关内容

最新更新