带有 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 converters(stallone
和 stallone2
),应用程序将始终选择第一个转换器,而不是适合响应类型的转换器当然会导致错误。
如何避免仅在小类型细节上有所不同的重复改造?
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()
,类型将为 GenericArrayType
,GenericComponentType
为 CustomElement.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.
我有一个 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 converters(stallone
和 stallone2
),应用程序将始终选择第一个转换器,而不是适合响应类型的转换器当然会导致错误。
如何避免仅在小类型细节上有所不同的重复改造?
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()
,类型将为 GenericArrayType
,GenericComponentType
为 CustomElement.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.