2015-10-29 32 views
14

Tôi có một dịch vụ REST HATEOAS (HAL) và quản lý để nói chuyện với nó với mã dưới đây (sử dụng như một công cụ chuyển đổi) nhưng khi tôi cố gắng merge the converters (stallonestallone2), ứng dụng sẽ luôn đón bộ chuyển đổi đầu tiên, thay vì bộ chuyển đổi thích hợp cho loại phản hồi mà tất nhiên dẫn đến lỗi.Nhiều bộ chuyển đổi với Retrofit 2

Làm cách nào để tránh các trang bị trùng lặp chỉ khác nhau trong một chi tiết loại nhỏ?

public interface Stallone { 
    @GET("/discovery") 
    Call<DiscoveryResponse> discover(); 
    @POST() 
    Call<LoginResponse> login(@Url String url, @Body LoginRequest secret); 
} 
public static void main(String... args) throws IOException { 
     // Initialize a converter for each supported (return) type 
     final Stallone stallone = new Retrofit.Builder() 
     .baseUrl(BASE) 
     .addConverterFactory(HALConverterFactory.create(DiscoveryResponse.class)) 
     .build().create(Stallone.class); 
     final Stallone stallone2 = new Retrofit.Builder() 
     .baseUrl(BASE) 
     .addConverterFactory(HALConverterFactory.create(LoginResponse.class)) 
     .build().create(Stallone.class); 

     // Follow the HAL links 
     Response<DiscoveryResponse> response = stallone.discover().execute(); 
     System.out.println(response.code() + " " + response.message()); 
     Assert.assertNotNull(response.body()); 
     String loginPath = response.body().getLogin(); 
     Assert.assertEquals(loginPath, "/login"); 

     // Follow another link 
     if (loginPath.startsWith("/")) 
     loginPath = loginPath.substring(1); 
     Response<LoginResponse> response2 = 
     stallone2.login(loginPath, 
         new LoginRequest(AUTH0TOKEN, null)).execute(); 
     System.out.println(response2.code() + " " + response2.message()); 
     Assert.assertNotNull(response2.body()); 

     String setupPath = response2.body().getSetup(); 
     Assert.assertEquals(setupPath, "/setup"); 

     System.out.println("All OK!"); 
    } 
public final class HALConverterFactory extends Converter.Factory { 

    private final Gson gson; 

    public static HALConverterFactory create(Class<?> type) { 
     return new HALConverterFactory(type); 
    } 

    private HALConverterFactory(Class<?> type) { 
     if (!HalResource.class.isAssignableFrom(type)) 
     throw new NullPointerException("Type should be a subclass of HalResource"); 
     GsonBuilder builder = new GsonBuilder(); 
     builder.registerTypeAdapter(HalResource.class, new HalSerializer()); 
     builder.registerTypeAdapter(HalResource.class, new HalDeserializer(type)); 
     builder.setExclusionStrategies(new HalExclusionStrategy()); 
     this.gson = builder.create(); 
    } 

    @Override 
    public Converter<ResponseBody, ?> fromResponseBody(Type type, Annotation[] annotations) { 
     return new HALResponseBodyConverter<>(gson); 
    } 

    @Override public Converter<?, RequestBody> toRequestBody(Type type, Annotation[] annotations) { 
     return new GsonRequestBodyConverter<>(gson, type); 
    } 
} 
final class HALResponseBodyConverter<T extends HalResource> 
    implements Converter<ResponseBody, T> { 
    private final Gson gson; 

    HALResponseBodyConverter(Gson gson) { 
     this.gson = gson; 
    } 

    @Override public T convert(ResponseBody value) throws IOException { 
     BufferedSource source = value.source(); 
     try { 
     String s = source.readString(Charset.forName("UTF-8")); 
     return (T) gson.fromJson(s, HalResource.class); 
     } catch (Exception e) { 
     throw new RuntimeException(e); 
     } finally { 
     closeQuietly(source); 
     } 
    } 

    private static void closeQuietly(Closeable closeable) { 
     if (closeable == null) return; 
     try { 
     closeable.close(); 
     } catch (IOException ignored) { 
     } 
    } 
} 

Một lần nữa, vấn đề là khi bạn cố gắng để rút ngắn ở trên như thế này:

final Stallone stallone = new Retrofit.Builder() 
    .baseUrl(BASE) 
.addConverterFactory(HALConverterFactory.create(DiscoveryResponse.class)) 
    .addConverterFactory(HALConverterFactory.create(LoginResponse.class)) 
    .build().create(Stallone.class); 

bạn sẽ nhận được một ngoại lệ tại Response<LoginResponse> response2 = ... dòng:

Exception in thread "main" java.lang.ClassCastException: com.example.retrofit.DiscoveryResponse không thể được đúc để com.example.retrofit.LoginResponse

+1

'GsonRequestBodyConverter' là gì? – naXa

Trả lời

18

Bạn cần phải trả lại null từ Converter.Factory nếu loại không khớp. Giữ Class<?> xung quanh trong một trường để so sánh nó với.

@Override 
public Converter<ResponseBody, ?> fromResponseBody(Type type, Annotation[] annotations) { 
    if (!this.type.equals(type)) { 
    return null; 
    } 
    return new HALResponseBodyConverter<>(gson); 
} 

Điều này sẽ cho phép nhiều trường hợp được sử dụng vì mỗi trường hợp chỉ áp dụng cho loại riêng của nó.

Điều đó nói rằng, tuy nhiên, bạn có thể có thể nhận được ngay với chỉ sử dụng một bộ chuyển đổi đơn và kéo lớp từ Type được thông qua tại.

@Override 
public Converter<ResponseBody, ?> fromResponseBody(Type type, Annotation[] annotations) { 
    if (!HALResponse.class.isAssignableFrom(type)) { 
    return null; 
    } 
    // TODO create converter with `type` now that you know what it is... 
} 

Bạn có thể nhìn vào bộ chuyển đổi Dây trong repo mà làm điều này cho một ví dụ đầy đủ.

0
package ch.halarious.core; 

import com.google.gson.JsonArray; 
import com.google.gson.JsonDeserializationContext; 
import com.google.gson.JsonElement; 
import com.google.gson.JsonObject; 
import com.google.gson.JsonParseException; 
import java.lang.reflect.Type; 
import java.util.ArrayList; 
import java.util.Iterator; 
import java.util.Map; 
import java.util.Set; 

/** 
* Custom Hal Deserializer 
* 
* @author jaren 
*/ 
public class CustomHalDeserializer extends HalDeserializer { 

    /** 
    * Intialisiert ein HalDeserializer-Objekt 
    * 
    * @param targetType Typ, den wir eigentlich deserialisieren sollten 
    */ 
    public CustomHalDeserializer(Class<?> targetType) { 
     super(targetType); 
    } 

    class CustomArrayList extends ArrayList implements HalResource{} 

    public HalResource deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context, Class<?> targetType) throws JsonParseException { 
     // Es handelt sich um ein JSON-Objekt. 
     JsonObject jsonObject = json.getAsJsonObject(); 
     JsonObject embeddedRoot = jsonObject.getAsJsonObject(HalConstants.EMBEDDED_ROOT); 

     if(embeddedRoot != null){ 
      Set<Map.Entry<String, JsonElement>> set = embeddedRoot.entrySet(); 
      if(set.toArray().length == 1){ 
       JsonArray ja = embeddedRoot.getAsJsonArray(set.iterator().next().getKey()); 
       if(ja.isJsonArray()) { 
        CustomArrayList arrayResult = new CustomArrayList(); 
        Iterator<JsonElement> i = ja.iterator(); 
        while(i.hasNext()){ 
         JsonElement je = i.next(); 
         arrayResult.add(super.deserialize(je, typeOfT, context, targetType)); 
        } 
        return arrayResult; 
       } 
      } 
     } 

     return super.deserialize(json, typeOfT, context, targetType); 
    } 
} 
0

tôi đã làm gần như giống nhau như @ jake-wharton nói trong https://stackoverflow.com/a/33459073/2055854 nhưng thêm một số thay đổi:

public class GenericConverterFactory<T> extends Converter.Factory { 

    private final Class<T> clazz; 

    public static GenericConverterFactory create(Class<T> clazz) { 
     return new GenericConverterFactory(clazz); 
    } 

    private GenericConverterFactory(Class<T> clazz) { 
     this.clazz = clazz; 
    } 

    @Override 
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { 
     if (!isNeededType(type)) { 
      return null; 
     } 

     // some converter that knows how to return your specific type T 
     return new GenericConverter(clazz); 
    } 

    private boolean isNeededType(Type type) { 
     if(type instanceof GenericArrayType) { 
      // if type is array we should check if it has the same component as our factory clazz 
      // if our factory clazz is not array getComponentType will return null 
      return ((GenericArrayType) type).getGenericComponentType().equals(clazz.getComponentType()); 
     } else if(clazz.getComponentType() == null) { 
      // if factory clazz is not array and type is not array too 
      // type is just a Class<?> and we should check if they are equal 
      return clazz.equals(type); 
     } else { 
      // otherwise our clazz is array and type is not 
      return false; 
     } 
    } 
} 

Loại đến từ giao diện trang bị thêm ví dụ nếu bạn có:

public interface SomeApi{ 
    @GET("customelement") 
    CustomElement[] getCustomElements(); 
    @GET("customelement/{id}") 
    CustomElement getCustomElement(@Path("id") int id); 
} 

Đối với phương thức getCustomElements() loại sẽ là GenericArrayType với GenericComponentTypeCustomElement.class và đối với loại phương thức thứ hai sẽ chỉ là CustomElement.class

Không chắc đó có phải là giải pháp tốt nhất hay không nhưng đối với tôi, nó hoạt động. Hy vọng nó giúp.

0

Trong trường hợp của tôi, tôi cần tuần tự hóa và chỉ deserialize một lớp thành XML. Đối với mọi thứ khác tôi cần Json.Vì vậy, tôi đã đăng ký adapter của tôi như thế này:

retrofit = new Retrofit.Builder() 
       .baseUrl(BuildConfig.BASE_URL) 
       .addConverterFactory(EditUserXmlConverterFactory.create()) 
       .addConverterFactory(GsonConverterFactory.create(createGson())) 
       .client(httpClient.build()) 
       .build(); 

kể từ khi tôi không thể mở rộng SimpleXmlConverterFactory (không may) Tôi đã phải sử dụng lớp của riêng tôi và thay đổi dòng sau:

if (!(type instanceof Class)) return null; 

để

if (type != NeedToBeXML.class) return null; 

Cách này chỉ các phản hồi và yêu cầu loại NeedToBeXML mới được chuyển đổi thành XML - và mọi thứ khác JSON.

Các vấn đề liên quan