【问题标题】:Jersey: hardcode POST/PUT ObjectMapper, without needing Content-Type headerJersey:硬编码 POST/PUT ObjectMapper,不需要 Content-Type 标头
【发布时间】:2016-12-27 04:46:16
【问题描述】:

我有一个 Jersey 1.19.1 资源,它实现了 @PUT@POST 方法。 @PUT 方法需要 JSON 字符串作为输入/请求正文,而 @POST 方法接受纯文本。 对于 JSON 映射,我使用的是 Jackson 2.8。

由于资源被定义为以这种方式工作,我不希望客户端被要求指定 Content-Type 请求标头,只是因为 Jersey 需要它来确定在请求正文上使用哪个 ObjectMapper .

我想要的是告诉 Jersey“将此 ObjectMapper 用于此 @PUT 输入”,或“始终假设此输入将在此方法上具有 application/json Content-Type。”

@Produces(MediaType.APPLICATION_JSON)
@Path("/some/endpoint/{id}")
public class MyResource {

    @PUT
    public JsonResult put(
        @PathParam("id") String id,
        // this should always be deserialized by Jackson, regardless of the `Content-Type` request header.
        JsonInput input
    ) {
        log.trace("PUT {}, {}, {}", id, input.foo, input.bar);
        return new JsonResult("PUT result");
    }

    @POST
    public JsonResult post(
        @PathParam("id") String id,
        // this should always be treated as plain text, regardless of the `Content-Type` request header.
        String input
    ) {
        log.trace("POST {}, {}", id, input);
        return new JsonResult("POST result");
    }
}

我只找到了this answer,但这不是我要找的,因为解决方案似乎是要求客户端添加正确的Content-Type 标头,或者手动执行对象映射。

【问题讨论】:

    标签: java json jersey jackson


    【解决方案1】:

    我设法想出了一个解决方法。我决定创建一个 ResourceFilter、对应的 ResourceFilterFactory 和一个注释类型,而不是声明要在 Jersey 资源方法上使用哪个 ObjectMapper。每当使用此类型注释资源类或方法时,ResourceFilter 会将请求的 Content-Type 覆盖为注释参数中声明的任何内容。

    这是我的代码:

    OverrideInputType注解:

    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface OverrideInputType {
        // What the Content-Type request header value should be replaced by
        String value();
    
        // which Content-Type request header values should not be replaced
        String[] except() default {};
    }
    

    OverrideInputTypeResourceFilter:

    public class OverrideInputTypeResourceFilter implements ResourceFilter, ContainerRequestFilter {
        private MediaType targetType;
        private Set<MediaType> exemptTypes;
    
        OverrideInputTypeResourceFilter(
            @Nonnull String targetType,
            @Nonnull String[] exemptTypes
        ) {
            this.targetType = MediaType.valueOf(targetType);
            this.exemptTypes = new HashSet<MediaType>(Lists.transform(
                Arrays.asList(exemptTypes),
                exemptType -> MediaType.valueOf(exemptType)
            ));
        }
    
        @Override
        public ContainerRequest filter(ContainerRequest request) {
            MediaType inputType = request.getMediaType();
            if (targetType.equals(inputType) || exemptTypes.contains(inputType)) {
                // unmodified
                return request;
            }
    
            MultivaluedMap<String, String> headers = request.getRequestHeaders();
            if (headers.containsKey(HttpHeaders.CONTENT_TYPE)) {
                headers.putSingle(HttpHeaders.CONTENT_TYPE, targetType.toString());
                request.setHeaders((InBoundHeaders)headers);
            }
            return request;
        }
    
        @Override
        public final ContainerRequestFilter getRequestFilter() {
            return this;
        }
    
        @Override
        public final ContainerResponseFilter getResponseFilter() {
            // don't filter responses
            return null;
        }
    }
    

    OverrideInputTypeResourceFilterFactory:

    public class OverrideInputTypeResourceFilterFactory implements ResourceFilterFactory {
    
        @Override
        public List<ResourceFilter> create(AbstractMethod am) {
            // documented to only be AbstractSubResourceLocator, AbstractResourceMethod, or AbstractSubResourceMethod
            if (am instanceof AbstractSubResourceLocator) {
                // not actually invoked per request, nothing to do
                log.debug("Ignoring AbstractSubResourceLocator {}", am);
                return null;
            } else if (am instanceof AbstractResourceMethod) {
                OverrideInputType annotation = am.getAnnotation(OverrideInputType.class);
                if (annotation == null) {
                    annotation = am.getResource().getAnnotation(OverrideInputType.class);
                }
                if (annotation != null) {
                    return Lists.<ResourceFilter>newArrayList(
                        new OverrideInputTypeResourceFilter(annotation.value(), annotation.except()));
                }
            } else {
                log.warn("Got an unexpected instance of {}: {}", am.getClass().getName(), am);
            }
            return null;
        }
    
    }
    

    示例MyResource 演示其用法:

    @Produces(MediaType.APPLICATION_JSON)
    @Path(/objects/{id}")
    public class MyResource {
        @PUT
    //  @Consumes(MediaType.APPLICATION_JSON)
        @OverrideInputType(MediaType.APPLICATION_JSON)
        public StatusResult put(@PathParam("id") int id, JsonObject obj) {
            log.trace("PUT {}", id);
            // do something with obj
            return new StatusResult(true);
        }
    
        @GET
        public JsonObject get(@PathParam("id") int id) {
            return new JsonObject(id);
        }
    }
    

    在 Jersey 2 中,您可以使用后匹配 ContainerRequestFilters 来做到这一点

    【讨论】:

      猜你喜欢
      • 2013-12-21
      • 2019-01-09
      • 1970-01-01
      • 2021-09-18
      • 1970-01-01
      • 2021-09-03
      • 2023-01-26
      • 2016-01-30
      • 2014-09-08
      相关资源
      最近更新 更多