Jersey 发送响应时未使用MessageBodyWriter

Jersey 发送响应时未使用MessageBodyWriter,jersey,jackson,jax-rs,dropwizard,Jersey,Jackson,Jax Rs,Dropwizard,我使用JacksonAnnotationIntrospector设置了一个自定义注释,以根据API版本吐出正确的属性名称。根据API版本,有一个助手类可以输出正确的ObjectMapper public class ObjectMapperFactory { private static final ObjectMapper objectMapper_V1 = new ObjectMapper().setAnnotationIntrospector(new VersioningProper

我使用JacksonAnnotationIntrospector设置了一个自定义注释,以根据API版本吐出正确的属性名称。根据API版本,有一个助手类可以输出正确的ObjectMapper

public class ObjectMapperFactory {

  private static final ObjectMapper objectMapper_V1 = new ObjectMapper().setAnnotationIntrospector(new VersioningPropertiesIntrospector(Entity.ApiVersion.V1));
  private static final ObjectMapper objectMapper_V2016 = new ObjectMapper().setAnnotationIntrospector(new VersioningPropertiesIntrospector(Entity.ApiVersion.V2016));

  public static ObjectMapper getObjectMapper(Entity.ApiVersion version) {
    switch (version) {
        case V1:
            return objectMapper_V1;

        case V2016:
            return objectMapper_V2016;

        case INVALID:
            return null;
    }

    return null;
  }
}
还有一个用于测试序列化的帮助函数

public static String serializeEntity(Entity.ApiVersion version, Object object) {
    try {
        return getObjectMapper(version).writeValueAsString(object);
    } catch (JsonProcessingException e) {
        log.error(e.toString());
    }

    return "Invalid API version.";
}
在这样的单元测试中:

@Test
public void testSerializeUserWithStateField() {
    User user = new User();
    user.setVersion(Entity.ApiVersion.V2016);
    user.setState(EntityState.CREATED.name());

    String userJson = serializeEntity(user.getVersion(), user);

    assertThat(userJson, equalTo("{\"lifecycleState\":\"CREATED\"}"));
}
@GET
@Path("users/{userId}")
public Response getUser(@PrincipalContext Principal principal,
                    @AuthorizationRequestContext AuthorizationRequest authorizationRequest,
                    @PathParam("userId") String userId) {

    final String decodedId = Optional
        .ofNullable(RequestValidationHelper.decodeUrlEncodedOCID(userId))
        .filter(StringUtils::isNotEmpty)
        .orElseThrow(BadArgumentException::new);

    User user = userStore.getUser(decodedId)
        .orElseThrow(OperationNotAllowedException::new);

    log.debug("Successfully retrieved user '{}'", decodedId);
    return Response.status(Response.Status.OK)
            .header(HttpHeaders.ETAG, user.getEtag())
            .entity(user)
            .build();
}
现在,假设我有这样的东西:

@Test
public void testSerializeUserWithStateField() {
    User user = new User();
    user.setVersion(Entity.ApiVersion.V2016);
    user.setState(EntityState.CREATED.name());

    String userJson = serializeEntity(user.getVersion(), user);

    assertThat(userJson, equalTo("{\"lifecycleState\":\"CREATED\"}"));
}
@GET
@Path("users/{userId}")
public Response getUser(@PrincipalContext Principal principal,
                    @AuthorizationRequestContext AuthorizationRequest authorizationRequest,
                    @PathParam("userId") String userId) {

    final String decodedId = Optional
        .ofNullable(RequestValidationHelper.decodeUrlEncodedOCID(userId))
        .filter(StringUtils::isNotEmpty)
        .orElseThrow(BadArgumentException::new);

    User user = userStore.getUser(decodedId)
        .orElseThrow(OperationNotAllowedException::new);

    log.debug("Successfully retrieved user '{}'", decodedId);
    return Response.status(Response.Status.OK)
            .header(HttpHeaders.ETAG, user.getEtag())
            .entity(user)
            .build();
}
用户扩展实体时:

public abstract class Entity {

  private String id;
  private String userId;

  @JsonIgnore
  private String etag;

  @VersioningProperties({
        @VersioningProperties.Property(version = ApiVersion.V1, value = "state"),
        @VersioningProperties.Property(version = ApiVersion.V2016, value = "lifecycleState")
})
  private String state;

  @JsonIgnore
  private ApiVersion version = ApiVersion.INVALID;

  public enum ApiVersion {
    INVALID,
    V1,
    V2016
  }
}
我知道映射程序单独返回正确的JSON。在构造响应时,我可以在.entity()中插入对serializeEntity的调用,但这会导致我们的测试出现问题,这些测试会检查响应中的实体是否为相同的类型(例如User)。例如,如果它们找到单个对象的序列化版本或任何对象的序列化列表的字符串,它们就会中断

如果我理解正确,那么在序列化指定对象时(我们使用Dropwizard和Jersey),应该选择并使用带有@Provider注释的MessageBodyWriter

但我们所做的只是打破了我们的每一个测试(500、409等)


我试图根据API版本更改的变量,
state
,在响应V2016 API调用时从未设置为
lifecycleState
。我怎样才能让它正常工作呢?

从你的例子中很难看出哪里出了问题

我为您编写了一个简单的示例,说明如何将其与DW集成

首先要注意的是:

注释MessageBodyWriter对您没有帮助。当您有一个处理类的注入框架时,这就可以工作了。您可以使用注释将其自动注册到Jersey,这就是此注释的作用。因此,在DW中(除非您使用Guicey或类路径扫描等),这将不起作用,您必须手动执行

首先,我的注释:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface VersioningProperties {    
    Property[] value();

    @interface Property {
        String version();
        String value();
    }
}
接下来,我的注释版本控制内容:)

这两个都是我从这篇文章中借用的:

模型:

public class Person {

    @VersioningProperties ( {
        @VersioningProperties.Property(version="A", value="test1")
        ,@VersioningProperties.Property(version="B", value="test2")
    })
    public String name = UUID.randomUUID().toString();

    public String x = "A"; // or B
}
我使用属性“x”来确定要使用哪个版本。其余部分与您的示例类似

因此,如果“x”是“A”,则属性名为“test1”,否则如果“B”,则属性名为“test2”

然后,应用程序按如下方式启动:

public class Application extends io.dropwizard.Application<Configuration>{

    @Override
    public void run(Configuration configuration, Environment environment) throws Exception {

        environment.jersey().register(HelloResource.class);

        ObjectMapper aMapper = environment.getObjectMapper().copy().setAnnotationIntrospector(new VersioningPropertiesIntrospector("A"));
        ObjectMapper bMapper = environment.getObjectMapper().copy().setAnnotationIntrospector(new VersioningPropertiesIntrospector("B"));
        environment.jersey().register(new MyMessageBodyWriter(aMapper, bMapper));
    }

    public static void main(String[] args) throws Exception {
        new Application().run("server", "/home/artur/dev/repo/sandbox/src/main/resources/config/test.yaml");
    }
}
我知道将主体传递到GET资源是不好的做法,但这只是为了切换Person属性以演示正在发生的事情

最后是我的MessageBodyWriter:

public class MyMessageBodyWriter implements MessageBodyWriter<Person> {

    private ObjectMapper aMapper;
    private ObjectMapper bMapper;

    MyMessageBodyWriter(ObjectMapper aMapper, ObjectMapper bMapper) {
        this.aMapper = aMapper;
        this.bMapper = bMapper;
    }

    @Override
    public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        return Person.class.isAssignableFrom(type);
    }

    @Override
    public long getSize(Person t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        return 0;
    }

    @Override
    public void writeTo(Person t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
            MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream)
            throws IOException, WebApplicationException {

        switch(t.x) {
        case "A": aMapper.writeValue(entityStream, t);
        break;
        case "B" : bMapper.writeValue(entityStream, t);
        break;
        }
    }
}
公共类MyMessageBodyWriter实现MessageBodyWriter{
私有对象映射器;
私有对象映射程序bMapper;
MyMessageBodyWriter(对象映射器aMapper、对象映射器bMapper){
this.aMapper=aMapper;
this.bMapper=bMapper;
}
@凌驾
公共布尔值可写(类类型、类型genericType、注释[]注释、MediaType MediaType){
返回Person.class.isAssignableFrom(类型);
}
@凌驾
public long getSize(Person t、类类型、类型genericType、注释[]注释、MediaType MediaType){
返回0;
}
@凌驾
public void writeTo(Person t,Class type,type genericType,Annotation[]Annotation,MediaType MediaType,
多值映射HttpHeader,OutputStream entityStream)
抛出IOException、WebApplicationException{
开关(t.x){
案例“A”:aMapper.writeValue(entityStream,t);
打破
案例“B”:B上诉书面价值(entityStream,t);
打破
}
}
}
现在,调用我的API,我得到:

artur@pandaadb:~/tmp/test$ curl -v -XGET "localhost:9085/api/test/asd"  -d "A"
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 9085 (#0)
> GET /api/test/asd HTTP/1.1
> Host: localhost:9085
> User-Agent: curl/7.47.0
> Accept: */*
> Content-Length: 1
> Content-Type: application/x-www-form-urlencoded
> 
* upload completely sent off: 1 out of 1 bytes
< HTTP/1.1 200 OK
< Date: Tue, 09 Aug 2016 09:59:13 GMT
< Content-Type: application/json
< Vary: Accept-Encoding
< Content-Length: 56
< 
* Connection #0 to host localhost left intact
{"x":"A","test1":"adec4590-47af-4eeb-a15a-67a532c22b72"}artur@pandaadb:~/tmp/test$ 
artur@pandaadb:~/tmp/test$ 
artur@pandaadb:~/tmp/test$ curl -v -XGET "localhost:9085/api/test/asd"  -d "B"
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 9085 (#0)
> GET /api/test/asd HTTP/1.1
> Host: localhost:9085
> User-Agent: curl/7.47.0
> Accept: */*
> Content-Length: 1
> Content-Type: application/x-www-form-urlencoded
> 
* upload completely sent off: 1 out of 1 bytes
< HTTP/1.1 200 OK
< Date: Tue, 09 Aug 2016 09:59:17 GMT
< Content-Type: application/json
< Vary: Accept-Encoding
< Content-Length: 56
< 
* Connection #0 to host localhost left intact
{"x":"B","test2":"6c56650c-6c87-418f-8b1a-0750a8091c46"}artur@pandaadb:~/tmp/test$ 
artur@pandaadb:~/tmp/test$curl-v-XGET“localhost:9085/api/test/asd”-d“A”
*正在尝试127.0.0.1。。。
*已连接到本地主机(127.0.0.1)端口9085(#0)
>GET/api/test/asd HTTP/1.1
>主机:本地主机:9085
>用户代理:curl/7.47.0
>接受:*/*
>内容长度:1
>内容类型:application/x-www-form-urlencoded
> 
*上传已完全发送:1个字节中有1个
GET/api/test/asd HTTP/1.1
>主机:本地主机:9085
>用户代理:curl/7.47.0
>接受:*/*
>内容长度:1
>内容类型:application/x-www-form-urlencoded
> 
*上传已完全发送:1个字节中有1个
请注意,根据传递给curl命令的主体,属性名称已正确切换

所以,我不能100%确定为什么你的测试会失败

我相信OM涉及到某种缓存,在这种缓存中,您不能来回切换AnnotationEntroSpector(这只是一种假设,因为我不能重置OM)。不管怎么说,最好只选两个不同的

我希望这能帮助你解决问题

如果您使用的是测试,那么还需要确保在单元测试中正确注册了所有内容

设置一些断点,sysout和其他有帮助的小朋友,他们会为你指出正确的方向

干杯


artur

从你的例子中很难看出哪里出了问题

我为您编写了一个简单的示例,说明如何将其与DW集成

首先要注意的是:

注释MessageBodyWriter对您没有帮助。当你有一个注射框的时候,这是有效的
public class MyMessageBodyWriter implements MessageBodyWriter<Person> {

    private ObjectMapper aMapper;
    private ObjectMapper bMapper;

    MyMessageBodyWriter(ObjectMapper aMapper, ObjectMapper bMapper) {
        this.aMapper = aMapper;
        this.bMapper = bMapper;
    }

    @Override
    public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        return Person.class.isAssignableFrom(type);
    }

    @Override
    public long getSize(Person t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        return 0;
    }

    @Override
    public void writeTo(Person t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
            MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream)
            throws IOException, WebApplicationException {

        switch(t.x) {
        case "A": aMapper.writeValue(entityStream, t);
        break;
        case "B" : bMapper.writeValue(entityStream, t);
        break;
        }
    }
}
artur@pandaadb:~/tmp/test$ curl -v -XGET "localhost:9085/api/test/asd"  -d "A"
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 9085 (#0)
> GET /api/test/asd HTTP/1.1
> Host: localhost:9085
> User-Agent: curl/7.47.0
> Accept: */*
> Content-Length: 1
> Content-Type: application/x-www-form-urlencoded
> 
* upload completely sent off: 1 out of 1 bytes
< HTTP/1.1 200 OK
< Date: Tue, 09 Aug 2016 09:59:13 GMT
< Content-Type: application/json
< Vary: Accept-Encoding
< Content-Length: 56
< 
* Connection #0 to host localhost left intact
{"x":"A","test1":"adec4590-47af-4eeb-a15a-67a532c22b72"}artur@pandaadb:~/tmp/test$ 
artur@pandaadb:~/tmp/test$ 
artur@pandaadb:~/tmp/test$ curl -v -XGET "localhost:9085/api/test/asd"  -d "B"
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 9085 (#0)
> GET /api/test/asd HTTP/1.1
> Host: localhost:9085
> User-Agent: curl/7.47.0
> Accept: */*
> Content-Length: 1
> Content-Type: application/x-www-form-urlencoded
> 
* upload completely sent off: 1 out of 1 bytes
< HTTP/1.1 200 OK
< Date: Tue, 09 Aug 2016 09:59:17 GMT
< Content-Type: application/json
< Vary: Accept-Encoding
< Content-Length: 56
< 
* Connection #0 to host localhost left intact
{"x":"B","test2":"6c56650c-6c87-418f-8b1a-0750a8091c46"}artur@pandaadb:~/tmp/test$