Jersey ContextResolver GetContext()只调用一次

时间:2016-01-17 13:12:43

标签: 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参数的映射器。

更新

每个uri路径都会调用

getContext()一次。例如,http://server/path1?pretty=true将为/ path1的所有请求生成相当大的输出,无论将来是什么queryParam。对path2的调用将再次调用getContext,但不会调用未来的path2调用。

UPDATE2

好吧,似乎每个类都会调用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;
        }
    }

}

2 个答案:

答案 0 :(得分:2)

你应该考虑表现。 使用您的解决方案,您将为每个请求创建一个新的ObjectMapper实例。这很重!!!在JProfile测量过程中,我发现ObjectMapper创建是主要的性能阻止。

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

答案 1 :(得分:-1)

如果您想要它,您需要为每次通话评估它。我在这里建议的是将这个逻辑移到一个专用组件中并执行以下操作:

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