带有 Retrofit 2 的多个转换器

Multiple converters with Retrofit 2

我有一个 HATEOAS (HAL) REST service and managed to talk to it with the code below (using halarious as a conversion engine) but when I try to merge the convertersstallonestallone2),应用程序将始终选择第一个转换器,而不是适合响应类型的转换器当然会导致错误。

如何避免仅在小类型细节上有所不同的重复改造?

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) {
      }
   }
}

同样,问题是 当您尝试像这样缩短上面的内容时:

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

你会在 Response<LoginResponse> response2 = ... 行得到一个异常:

Exception in thread "main" java.lang.ClassCastException: com.example.retrofit.DiscoveryResponse cannot be cast to com.example.retrofit.LoginResponse

如果类型不匹配,您需要从 Converter.Factory return null。将 Class<?> 放在一个字段中,以便与它进行比较。

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

这将允许使用多个实例,因为每个实例只适用于它自己的类型。

也就是说,但是,您可能只使用一个转换器并从传入的 Type 中提取 class。

@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...
}

您可以查看 repo 中的 Wire converter 以获取完整示例。

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

我做的几乎与@jake-wharton 在 中所说的一样,但添加了一些更改:

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

类型来自改装界面,例如,如果您有:

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

对于方法 getCustomElements(),类型将为 GenericArrayTypeGenericComponentTypeCustomElement.class,对于第二种方法,类型将为 CustomElement.class

不确定这是否是最佳解决方案,但对我来说它有效。希望对你有帮助。

在我的例子中,我只需要序列化和反序列化一个 class 到 XML。对于我需要的其他一切 Json。所以我这样注册了我的适配器:

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

因为我无法扩展 SimpleXmlConverterFactory(不幸的是)我不得不使用自己的 class 并更改以下行:

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

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

通过这种方式,只有 NeedToBeXML 类型的响应和请求会转换为 XML - 其他所有内容 JSON.