2016-08-03 60 views
0

我有一個用JacksonAnnotationIntrospector設置的自定義註釋,根據API版本吐出正確的屬性名稱。還有一個助手類,它再次根據API版本吐出正確的ObjectMapper。發送回覆時未使用MessageBodyWriter

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(); 
} 

其中用戶擴展實體:

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。在構造Responses時,我可以在.entity()中插入對serializeEntity的調用,但是這導致我們的測試出現問題,這些問題檢查Response中的實體是否是相同類型(例如User)。如果他們發現單個對象的序列化版本或任何對象的序列化列表的字符串,例如,它們會中斷。

如果我正確理解它,應該挑選一個帶有@Provider註解的MessageBodyWriter,並在序列化指定對象(我們使用Dropwizard和Jersey)時使用它。

@Provider 
public class EntityMessageBodyWriter implements MessageBodyWriter<Entity> { 
    @Override 
    public long getSize(Entity entity, Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType) { 
    return 0; 
    } 

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

    @Override 
    public void writeTo(Entity entity, Class<?> aClass, Type type, Annotation[] annotations, 
        MediaType mediaType, MultivaluedMap<String, Object> multivaluedMap, OutputStream outputStream) 
     throws IOException, WebApplicationException { 
    outputStream.write(serializeEntity(entity.getVersion(), entity).getBytes()); 
    } 
} 

但是,情況並非如此。我沒有爲每個對象創建一個單獨的MessageBodyWriter,因爲文檔說你可以使用超類,並且所有的子類都會匹配(假設你在isWriteable()函數中返回true)。我還嘗試過使用JSON媒體類型指定@Produces並指定一個子類(如User)而不是Entity,但似乎沒有任何效果。

我也試圖與註冊MessageBodyWriter:

JerseyEnvironment jersey = env.jersey(); 
jersey.register(new IdentityEntityMessageBodyWriter()); 

但所有做的是打破幾乎每一個測試,我們有(500S,409S,等等)。

我試圖根據API版本state更改的變量在響應V2016 API調用時從未設置爲lifecycleState。我怎樣才能使這個工作正常?

+0

嗨,你可以添加拋出你的問題的異常? – pandaadb

+0

實際上並沒有例外。它編譯並運行,但它只是吐出屬性名稱的錯誤版本。 – Gemini14

+0

你的消息是否被調用?在serializeEntity中設置斷點時,發生了什麼?這是一個生產問題(例如,您在運行服務器時看到這種情況是否失敗)還是集成測試問題? – pandaadb

回答

2

從你的例子中弄清楚發生了什麼問題有點難。

我寫了一個最簡單的例子,說明如何將它與DW集成。

首先要注意:

註解MessageBodyWriter不會幫你。當你有一個處理你的類的注入框架時,這是有效的。您可以使用Annotation自動將其註冊到Jersey,這是註釋的作用。所以在DW中(除非你使用Guicey或類路徑掃描等),這是行不通的,你必須手動完成。

首先,我譯註:

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

    @interface Property { 
     String version(); 
     String value(); 
    } 
} 

接下來,我的註解版本的thingie :)

public class VersioningPropertiesIntrospector extends JacksonAnnotationIntrospector { 

    private static final long serialVersionUID = 1L; 
    private String version; 

    public VersioningPropertiesIntrospector(String version) { 
     this.version = version; 
    } 

    @Override 
    public PropertyName findNameForSerialization(Annotated a) { 
     PropertyName propertyName = findNameFromVersioningProperties(a); 
     if (propertyName != null) { 
      return propertyName; 
     } 
     return super.findNameForSerialization(a); 
    } 

    @Override 
    public PropertyName findNameForDeserialization(Annotated a) { 
     PropertyName propertyName = findNameFromVersioningProperties(a); 
     if (propertyName != null) { 
      return propertyName; 
     } 
     return super.findNameForDeserialization(a); 
    } 

    private PropertyName findNameFromVersioningProperties(Annotated a) { 
     VersioningProperties annotation = a.getAnnotation(VersioningProperties.class); 
     if (annotation == null) { 
      return null; 
     } 
     for (Property property : annotation.value()) { 
      if (version.equals(property.version())) { 
       return new PropertyName(property.value()); 
      } 
     } 
     return null; 
    } 

} 

這兩個我從這篇文章借用:Specifying different JSON property names according to API version with Jackson

型號:

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"); 
    } 
} 

注意我註冊與球衣環境MessageBodyWriter。我也使用DW已經提供給我們的ObjectMapper。這個OM有一些已經設置和有用的配置(例如DateTime處理和類似的功能)。

我的資源:

@Path("test") 
public class HelloResource { 

    @GET 
    @Path("asd") 
    @Produces(MediaType.APPLICATION_JSON) 
    public Person p(String x) { 
     Person p = new Person(); 
     p.x = x; 
     return p; 
    } 
} 

我知道這是不好的做法,通過一個身體成GET資源,但這只是這樣我就可以開關的人員財產證明發生了什麼。

這終於是我的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; 
     } 
    } 
} 

現在,打電話給我的API,我得到:

[email protected]:~/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"}[email protected]:~/tmp/test$ 
[email protected]:~/tmp/test$ 
[email protected]:~/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"}[email protected]:~/tmp/test$ 

注意,屬性名稱已根據我傳遞到身體正常切換我捲曲命令。

所以,我不是100%確定你的測試失敗的原因。

我相信有一些與OM有關的緩存,你無法來回切換AnnotationIntrospector(這只是一個假設,因爲我不能重置我的OM)。無論如何,這可能是一個更好的選擇。

我希望這可以幫助你解決你的問題。

如果您正在使用測試,您需要確保所有單元測試中的所有內容都已正確註冊。

設置一些斷點,sysout和其他有用的小朋友,他們會指出你正確的事情。

乾杯!

artur