我有以下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 的逻辑