【问题标题】:Jersey ContextResolver GetContext() called only onceJersey ContextResolver GetContext() 仅调用一次
【发布时间】:2016-04-22 16:16:48
【问题描述】:

我有以下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 的所有请求产生漂亮的输出,无论它们未来如何漂亮的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;
        }
    }

}

【问题讨论】:

  • 所以你想为每个请求确定使用哪个映射器?

标签: java jersey jackson jax-rs


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

    • 这是一个简单的解决方案,不可扩展。添加漂亮过滤器的功能应应用于基于queryParams 的所有请求。向每个请求添加建议的逻辑不是 - IMO - 一个好的软件设计。 ContextResolver 应该解决这个确切的问题。 getContext() 应该按照设计在每个请求中调用。我发布的示例代码是基于官方文档的,所以它应该可以工作。
    • 很抱歉,我看不到任何地方都为每个请求调用 getContext()。我猜你的假设是错误的,你正在寻找错误的方法!
    • 另外,ContainerResponseFilter 可能是您正在寻找的(在每个请求之后执行的过滤器,您可以在其中决定是否输出漂亮的打印)
    • getContext() 应该为每个请求调用,请参阅我更新的问题。 ContainerResponseFilter是在Entity转化为json之后调用的,所以并不是真正处理它的地方。
    • 另外,ContainerResponseFilter 不能将日期转换为 UTC。它不会公开 objectMapper 对象,因此会限制您的 JSON 能力。
    猜你喜欢
    • 1970-01-01
    • 2013-01-21
    • 1970-01-01
    • 2023-01-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-17
    • 2017-04-02
    相关资源
    最近更新 更多