From c726a3adec8a5ec56e283fa2064ebb22db91216c Mon Sep 17 00:00:00 2001 From: chentianming Date: Sat, 11 Jun 2022 08:58:36 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81reactor=E3=80=81rxjava?= =?UTF-8?q?=E5=93=8D=E5=BA=94=E5=BC=8F=E7=BC=96=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 111 +++++++++------ pom.xml | 22 ++- .../config/RetrofitAutoConfiguration.java | 21 --- .../boot/config/RetrofitProperties.java | 11 +- .../boot/core/BasicTypeConverterFactory.java | 5 + .../boot/core/BodyCallAdapterFactory.java | 5 + .../boot/core/ResponseCallAdapterFactory.java | 5 + .../spring/boot/core/RetrofitFactoryBean.java | 51 +++++++ .../core/reactive/MonoCallAdapterFactory.java | 133 +++++++++++++++++ .../Rxjava2CompletableCallAdapterFactory.java | 70 +++++++++ .../Rxjava2SingleCallAdapterFactory.java | 134 ++++++++++++++++++ .../Rxjava3CompletableCallAdapterFactory.java | 70 +++++++++ .../Rxjava3SingleCallAdapterFactory.java | 134 ++++++++++++++++++ .../spring/boot/test/http/HttpApi.java | 62 ++++---- .../completable/Rxjava2CompletableTest.java | 65 +++++++++ .../Rxjava2CompletableTestApi.java | 16 +++ .../completable/Rxjava3CompletableTest.java | 65 +++++++++ .../Rxjava3CompletableTestApi.java | 16 +++ .../boot/test/reactive/mono/MonoTest.java | 126 ++++++++++++++++ .../boot/test/reactive/mono/MonoTestApi.java | 26 ++++ .../reactive/single/Rxjava2SingleTest.java | 113 +++++++++++++++ .../reactive/single/Rxjava2SingleTestApi.java | 23 +++ .../reactive/single/Rxjava3SingleTest.java | 113 +++++++++++++++ .../reactive/single/Rxjava3SingleTestApi.java | 23 +++ src/test/resources/application.yml | 7 +- src/test/resources/default-config.yml | 3 - 26 files changed, 1320 insertions(+), 110 deletions(-) create mode 100644 src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/reactive/MonoCallAdapterFactory.java create mode 100644 src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/reactive/Rxjava2CompletableCallAdapterFactory.java create mode 100644 src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/reactive/Rxjava2SingleCallAdapterFactory.java create mode 100644 src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/reactive/Rxjava3CompletableCallAdapterFactory.java create mode 100644 src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/reactive/Rxjava3SingleCallAdapterFactory.java create mode 100644 src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/completable/Rxjava2CompletableTest.java create mode 100644 src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/completable/Rxjava2CompletableTestApi.java create mode 100644 src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/completable/Rxjava3CompletableTest.java create mode 100644 src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/completable/Rxjava3CompletableTestApi.java create mode 100644 src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/mono/MonoTest.java create mode 100644 src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/mono/MonoTestApi.java create mode 100644 src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/single/Rxjava2SingleTest.java create mode 100644 src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/single/Rxjava2SingleTestApi.java create mode 100644 src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/single/Rxjava3SingleTest.java create mode 100644 src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/single/Rxjava3SingleTestApi.java diff --git a/README.md b/README.md index 2bfbb96..9554d2f 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ gitee项目地址:[https://gitee.com/lianjiatech/retrofit-spring-boot-starter] com.github.lianjiatech retrofit-spring-boot-starter - 2.3.4 + 2.3.5 ``` @@ -59,7 +59,7 @@ gitee项目地址:[https://gitee.com/lianjiatech/retrofit-spring-boot-starter] com.github.lianjiatech retrofit-spring-boot-starter - 2.3.4 + 2.3.5 com.squareup.okhttp3 @@ -145,14 +145,11 @@ public class TestService { ```yaml retrofit: - # 全局转换器工厂 + # 全局转换器工厂(已经内置了组件扩展的转换器工厂,这里请勿重复配置) global-converter-factories: - - com.github.lianjiatech.retrofit.spring.boot.core.BasicTypeConverterFactory - retrofit2.converter.jackson.JacksonConverterFactory - # 全局调用适配器工厂 + # 全局调用适配器工厂(已经内置了组件扩展的调用适配器工厂,这里请勿重复配置) global-call-adapter-factories: - - com.github.lianjiatech.retrofit.spring.boot.core.BodyCallAdapterFactory - - com.github.lianjiatech.retrofit.spring.boot.core.ResponseCallAdapterFactory # 全局日志打印配置 global-log: @@ -778,52 +775,72 @@ public class SourceGlobalInterceptor implements GlobalInterceptor { - `Response`: 将响应内容适配成`Response`对象返回!(不推荐) - `Call`: 不执行适配处理,直接返回`Call`对象!(不推荐) +**响应式编程支持**: + +- `Mono`: project-reactor响应式返回类型 +- `Single`:rxjava响应式返回类型(支持rxjava2/rxjava3) +- `Completable`:rxjava响应式返回类型,http请求没有响应体(支持rxjava2/rxjava3) + + ```java +@RetrofitClient(baseUrl = "${test.baseUrl}") +public interface HttpApi { - /** - * 其他任意Java类型 - * 将响应体内容适配成一个对应的Java类型对象返回,如果http状态码不是2xx,直接抛错! - * @param id - * @return - */ - @GET("person") - Result getPerson(@Query("id") Long id); + /** + * 基础类型(`String`/`Long`/`Integer`/`Boolean`/`Float`/`Double`):直接将响应内容转换为上述基础类型。 + */ + @POST("getString") + String getString(@Body Person person); - /** - * CompletableFuture - * 将响应体内容适配成CompletableFuture对象返回 - * @param id - * @return - */ - @GET("person") - CompletableFuture> getPersonCompletableFuture(@Query("id") Long id); + /** + * 其它任意POJO类型: 将响应体内容适配成一个对应的POJO类型对象返回,如果http状态码不是2xx,直接抛错! + */ + @GET("person") + Result getPerson(@Query("id") Long id); - /** - * Void - * 不关注返回类型可以使用Void。如果http状态码不是2xx,直接抛错! - * @param id - * @return - */ - @GET("person") - Void getPersonVoid(@Query("id") Long id); + /** + * `CompletableFuture` :将响应体内容适配成CompletableFuture对象返回,异步调用 + */ + @GET("person") + CompletableFuture> getPersonCompletableFuture(@Query("id") Long id); - /** - * Response - * 将响应内容适配成Response对象返回 - * @param id - * @return - */ - @GET("person") - Response> getPersonResponse(@Query("id") Long id); + /** + * `Void`: 不关注返回类型可以使用`Void`,如果http状态码不是2xx,直接抛错! + */ + @POST("savePerson") + Void savePersonVoid(@Body Person person); - /** - * Call - * 不执行适配处理,直接返回Call对象 - * @param id - * @return - */ - @GET("person") - Call> getPersonCall(@Query("id") Long id); + /** + * `Response`:将响应内容适配成Response对象返回 + */ + @GET("person") + Response> getPersonResponse(@Query("id") Long id); + + /** + * `Call`:不执行适配处理,直接返回Call对象 + */ + @GET("person") + Call> getPersonCall(@Query("id") Long id); + + + /** + * `Mono` : project-reactor响应式返回类型 + */ + @GET("person") + Mono> monoPerson(@Query("id") Long id); + + /** + * `Single`:rxjava响应式返回类型(支持rxjava2/rxjava3) + */ + @GET("person") + Single> singlePerson(@Query("id") Long id); + + /** + * `Completable`:rxjava响应式返回类型,http请求没有响应体(支持rxjava2/rxjava3) + */ + @GET("ping") + Completable ping(); +} ``` diff --git a/pom.xml b/pom.xml index 5163ebc..f044f7f 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.github.lianjiatech retrofit-spring-boot-starter - 2.3.4 + 2.3.5 retrofit-spring-boot-starter retrofit-spring-boot-starter @@ -172,6 +172,26 @@ 1.7.1 provided + + io.projectreactor + reactor-core + 3.3.22.RELEASE + provided + + + io.reactivex.rxjava3 + rxjava + 3.1.5 + provided + + + + io.reactivex.rxjava2 + rxjava + 2.2.21 + provided + + diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/config/RetrofitAutoConfiguration.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/config/RetrofitAutoConfiguration.java index 429706e..0e7ac31 100644 --- a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/config/RetrofitAutoConfiguration.java +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/config/RetrofitAutoConfiguration.java @@ -15,12 +15,9 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.lianjiatech.retrofit.spring.boot.core.AutoConfiguredRetrofitScannerRegistrar; -import com.github.lianjiatech.retrofit.spring.boot.core.BasicTypeConverterFactory; -import com.github.lianjiatech.retrofit.spring.boot.core.BodyCallAdapterFactory; import com.github.lianjiatech.retrofit.spring.boot.core.Constants; import com.github.lianjiatech.retrofit.spring.boot.core.ErrorDecoder; import com.github.lianjiatech.retrofit.spring.boot.core.PathMatchInterceptorBdfProcessor; -import com.github.lianjiatech.retrofit.spring.boot.core.ResponseCallAdapterFactory; import com.github.lianjiatech.retrofit.spring.boot.core.RetrofitFactoryBean; import com.github.lianjiatech.retrofit.spring.boot.core.ServiceInstanceChooser; import com.github.lianjiatech.retrofit.spring.boot.core.SourceOkHttpClientRegistrar; @@ -70,24 +67,6 @@ public SourceOkHttpClientRegistry sourceOkHttpClientRegistry( return new SourceOkHttpClientRegistry(sourceOkHttpClientRegistrars); } - @Bean - @ConditionalOnMissingBean - public BodyCallAdapterFactory bodyCallAdapterFactory() { - return new BodyCallAdapterFactory(); - } - - @Bean - @ConditionalOnMissingBean - public ResponseCallAdapterFactory responseCallAdapterFactory() { - return new ResponseCallAdapterFactory(); - } - - @Bean - @ConditionalOnMissingBean - public BasicTypeConverterFactory basicTypeConverterFactory() { - return new BasicTypeConverterFactory(); - } - @Bean @ConditionalOnMissingBean public ErrorDecoder.DefaultErrorDecoder defaultErrorDecoder() { diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/config/RetrofitProperties.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/config/RetrofitProperties.java index 3cc1ca7..83096d7 100644 --- a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/config/RetrofitProperties.java +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/config/RetrofitProperties.java @@ -3,10 +3,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.NestedConfigurationProperty; -import com.github.lianjiatech.retrofit.spring.boot.core.BasicTypeConverterFactory; -import com.github.lianjiatech.retrofit.spring.boot.core.BodyCallAdapterFactory; import com.github.lianjiatech.retrofit.spring.boot.core.Constants; -import com.github.lianjiatech.retrofit.spring.boot.core.ResponseCallAdapterFactory; import com.github.lianjiatech.retrofit.spring.boot.degrade.DegradeProperty; import com.github.lianjiatech.retrofit.spring.boot.log.GlobalLogProperty; import com.github.lianjiatech.retrofit.spring.boot.retry.GlobalRetryProperty; @@ -49,15 +46,13 @@ public class RetrofitProperties { * global converter factories, The converter instance is first obtained from the Spring container. If it is not obtained, it is created by reflection. */ @SuppressWarnings("unchecked") - private Class[] globalConverterFactories = (Class< - ? extends Converter.Factory>[])new Class[] {BasicTypeConverterFactory.class, JacksonConverterFactory.class}; + private Class[] globalConverterFactories = + (Class[])new Class[] {JacksonConverterFactory.class}; /** * 全局调用适配器工厂,转换器实例优先从Spring容器获取,如果没有获取到,则反射创建。 * global call adapter factories, The callAdapter instance is first obtained from the Spring container. If it is not obtained, it is created by reflection. */ @SuppressWarnings("unchecked") - private Class[] globalCallAdapterFactories = - (Class[])new Class[] {BodyCallAdapterFactory.class, - ResponseCallAdapterFactory.class}; + private Class[] globalCallAdapterFactories = new Class[0]; } diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/BasicTypeConverterFactory.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/BasicTypeConverterFactory.java index 2cfb93b..10378d6 100644 --- a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/BasicTypeConverterFactory.java +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/BasicTypeConverterFactory.java @@ -4,6 +4,8 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Type; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; import okhttp3.RequestBody; import okhttp3.ResponseBody; import retrofit2.Converter; @@ -12,8 +14,11 @@ /** * @author 陈添明 */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) public final class BasicTypeConverterFactory extends Converter.Factory { + public static final BasicTypeConverterFactory INSTANCE = new BasicTypeConverterFactory(); + @Override public Converter requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/BodyCallAdapterFactory.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/BodyCallAdapterFactory.java index 92f1a8f..8c361dd 100644 --- a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/BodyCallAdapterFactory.java +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/BodyCallAdapterFactory.java @@ -20,6 +20,8 @@ import com.github.lianjiatech.retrofit.spring.boot.exception.RetrofitException; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; import okhttp3.Request; import okhttp3.ResponseBody; import retrofit2.Call; @@ -35,8 +37,11 @@ * * @author 陈添明 */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) public final class BodyCallAdapterFactory extends CallAdapter.Factory { + public static final BodyCallAdapterFactory INSTANCE = new BodyCallAdapterFactory(); + @Override public CallAdapter get(Type returnType, Annotation[] annotations, Retrofit retrofit) { if (Call.class.isAssignableFrom(getRawType(returnType))) { diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/ResponseCallAdapterFactory.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/ResponseCallAdapterFactory.java index 610f898..ec9702e 100644 --- a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/ResponseCallAdapterFactory.java +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/ResponseCallAdapterFactory.java @@ -22,6 +22,8 @@ import com.github.lianjiatech.retrofit.spring.boot.exception.RetrofitException; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; import okhttp3.Request; import retrofit2.Call; import retrofit2.CallAdapter; @@ -34,8 +36,11 @@ * * @author 陈添明 */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) public final class ResponseCallAdapterFactory extends CallAdapter.Factory { + public static final ResponseCallAdapterFactory INSTANCE = new ResponseCallAdapterFactory(); + @Override public CallAdapter get(Type returnType, Annotation[] annotations, Retrofit retrofit) { if (Response.class.isAssignableFrom(getRawType(returnType))) { diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/RetrofitFactoryBean.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/RetrofitFactoryBean.java index fed82ea..579005b 100644 --- a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/RetrofitFactoryBean.java +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/RetrofitFactoryBean.java @@ -21,6 +21,11 @@ import org.springframework.util.StringUtils; import com.github.lianjiatech.retrofit.spring.boot.config.RetrofitConfigBean; +import com.github.lianjiatech.retrofit.spring.boot.core.reactive.MonoCallAdapterFactory; +import com.github.lianjiatech.retrofit.spring.boot.core.reactive.Rxjava2CompletableCallAdapterFactory; +import com.github.lianjiatech.retrofit.spring.boot.core.reactive.Rxjava2SingleCallAdapterFactory; +import com.github.lianjiatech.retrofit.spring.boot.core.reactive.Rxjava3CompletableCallAdapterFactory; +import com.github.lianjiatech.retrofit.spring.boot.core.reactive.Rxjava3SingleCallAdapterFactory; import com.github.lianjiatech.retrofit.spring.boot.degrade.DegradeProxy; import com.github.lianjiatech.retrofit.spring.boot.degrade.RetrofitDegrade; import com.github.lianjiatech.retrofit.spring.boot.interceptor.BasePathMatchInterceptor; @@ -162,12 +167,58 @@ private Retrofit createRetrofit() { combineAndCreate(retrofitClient.callAdapterFactories(), retrofitConfigBean.getGlobalCallAdapterFactoryClasses()) .forEach(retrofitBuilder::addCallAdapterFactory); + addReactiveCallAdapterFactory(retrofitBuilder); + retrofitBuilder.addCallAdapterFactory(ResponseCallAdapterFactory.INSTANCE); + retrofitBuilder.addCallAdapterFactory(BodyCallAdapterFactory.INSTANCE); + retrofitBuilder.addConverterFactory(BasicTypeConverterFactory.INSTANCE); combineAndCreate(retrofitClient.converterFactories(), retrofitConfigBean.getGlobalConverterFactoryClasses()) .forEach(retrofitBuilder::addConverterFactory); + return retrofitBuilder.build(); } + private void addReactiveCallAdapterFactory(Retrofit.Builder retrofitBuilder) { + if (reactor3ClassExist()) { + retrofitBuilder.addCallAdapterFactory(MonoCallAdapterFactory.INSTANCE); + } + if (rxjava2CalssExist()) { + retrofitBuilder.addCallAdapterFactory(Rxjava2SingleCallAdapterFactory.INSTANCE); + retrofitBuilder.addCallAdapterFactory(Rxjava2CompletableCallAdapterFactory.INSTANCE); + } + if (rxjava3ClassExist()) { + retrofitBuilder.addCallAdapterFactory(Rxjava3SingleCallAdapterFactory.INSTANCE); + retrofitBuilder.addCallAdapterFactory(Rxjava3CompletableCallAdapterFactory.INSTANCE); + } + } + + private boolean rxjava3ClassExist() { + try { + Class.forName("io.reactivex.rxjava3.core.Single"); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } + + private boolean rxjava2CalssExist() { + try { + Class.forName("io.reactivex.Single"); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } + + private boolean reactor3ClassExist() { + try { + Class.forName("reactor.core.publisher.Mono"); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } + private List combineAndCreate(Class[] clz, Class[] globalClz) { if (clz.length == 0 && globalClz.length == 0) { return Collections.emptyList(); diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/reactive/MonoCallAdapterFactory.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/reactive/MonoCallAdapterFactory.java new file mode 100644 index 0000000..1d691ff --- /dev/null +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/reactive/MonoCallAdapterFactory.java @@ -0,0 +1,133 @@ +package com.github.lianjiatech.retrofit.spring.boot.core.reactive; + +import java.lang.annotation.Annotation; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import reactor.core.publisher.Mono; +import reactor.core.publisher.MonoSink; +import retrofit2.Call; +import retrofit2.CallAdapter; +import retrofit2.Callback; +import retrofit2.HttpException; +import retrofit2.Response; +import retrofit2.Retrofit; + +/** + * @author 陈添明 + * @since 2022/6/9 8:53 下午 + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class MonoCallAdapterFactory extends CallAdapter.Factory { + + public static final MonoCallAdapterFactory INSTANCE = new MonoCallAdapterFactory(); + + @Override + public CallAdapter get(Type returnType, Annotation[] annotations, Retrofit retrofit) { + if (getRawType(returnType) != Mono.class) { + return null; + } + if (!(returnType instanceof ParameterizedType)) { + throw new IllegalStateException( + "Mono return type must be parameterized" + + " as Mono or Mono"); + } + Type innerType = getParameterUpperBound(0, (ParameterizedType)returnType); + + if (getRawType(innerType) != Response.class) { + // Generic type is not Response. Use it for body-only adapter. + return new BodyCallAdapter<>(innerType); + } + + // Generic type is Response. Extract T and create the Response version of the adapter. + if (!(innerType instanceof ParameterizedType)) { + throw new IllegalStateException( + "Response must be parameterized" + " as Response or Response"); + } + Type responseType = getParameterUpperBound(0, (ParameterizedType)innerType); + return new ResponseCallAdapter<>(responseType); + } + + private static class BodyCallAdapter implements CallAdapter> { + + private final Type responseType; + + public BodyCallAdapter(Type responseType) { + this.responseType = responseType; + } + + @Override + public Type responseType() { + return responseType; + } + + @Override + public Mono adapt(Call call) { + return Mono.create(monoSink -> call.enqueue(new BodyCallback(monoSink))); + } + + private class BodyCallback implements Callback { + + private final MonoSink monoSink; + + public BodyCallback(MonoSink monoSink) { + this.monoSink = monoSink; + } + + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + monoSink.success(response.body()); + } else { + monoSink.error(new HttpException(response)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + monoSink.error(t); + } + } + + } + + private static class ResponseCallAdapter implements CallAdapter>> { + + private final Type responseType; + + public ResponseCallAdapter(Type responseType) { + this.responseType = responseType; + } + + @Override + public Type responseType() { + return responseType; + } + + @Override + public Mono> adapt(Call call) { + return Mono.create(monoSink -> call.enqueue(new ResponseCallback(monoSink))); + } + + private class ResponseCallback implements Callback { + + private final MonoSink> monoSink; + + public ResponseCallback(MonoSink> monoSink) { + this.monoSink = monoSink; + } + + @Override + public void onResponse(Call call, Response response) { + monoSink.success(response); + } + + @Override + public void onFailure(Call call, Throwable t) { + monoSink.error(t); + } + } + } +} diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/reactive/Rxjava2CompletableCallAdapterFactory.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/reactive/Rxjava2CompletableCallAdapterFactory.java new file mode 100644 index 0000000..ffeefc3 --- /dev/null +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/reactive/Rxjava2CompletableCallAdapterFactory.java @@ -0,0 +1,70 @@ +package com.github.lianjiatech.retrofit.spring.boot.core.reactive; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; + +import io.reactivex.Completable; +import io.reactivex.CompletableEmitter; +import io.reactivex.annotations.NonNull; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import retrofit2.Call; +import retrofit2.CallAdapter; +import retrofit2.Callback; +import retrofit2.HttpException; +import retrofit2.Response; +import retrofit2.Retrofit; + +/** + * @author 陈添明 + * @since 2022/6/10 8:08 上午 + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class Rxjava2CompletableCallAdapterFactory extends CallAdapter.Factory { + + public static final Rxjava2CompletableCallAdapterFactory INSTANCE = new Rxjava2CompletableCallAdapterFactory(); + + @Override + public CallAdapter get(Type returnType, Annotation[] annotations, Retrofit retrofit) { + if (getRawType(returnType) != Completable.class) { + return null; + } + return new NonBodyCallAdapter<>(); + } + + private static class NonBodyCallAdapter implements CallAdapter { + + @Override + public Type responseType() { + return Void.class; + } + + @Override + public Completable adapt(Call call) { + return Completable.create(emitter -> call.enqueue(new NonBodyCallBack(emitter))); + } + + private class NonBodyCallBack implements Callback { + + private final CompletableEmitter emitter; + + public NonBodyCallBack(@NonNull CompletableEmitter emitter) { + this.emitter = emitter; + } + + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + emitter.onComplete(); + } else { + emitter.onError(new HttpException(response)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + emitter.onError(t); + } + } + } +} diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/reactive/Rxjava2SingleCallAdapterFactory.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/reactive/Rxjava2SingleCallAdapterFactory.java new file mode 100644 index 0000000..b159932 --- /dev/null +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/reactive/Rxjava2SingleCallAdapterFactory.java @@ -0,0 +1,134 @@ +package com.github.lianjiatech.retrofit.spring.boot.core.reactive; + +import java.lang.annotation.Annotation; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +import io.reactivex.Single; +import io.reactivex.SingleEmitter; +import io.reactivex.annotations.NonNull; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import retrofit2.Call; +import retrofit2.CallAdapter; +import retrofit2.Callback; +import retrofit2.HttpException; +import retrofit2.Response; +import retrofit2.Retrofit; + +/** + * @author 陈添明 + * @since 2022/6/10 8:08 上午 + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class Rxjava2SingleCallAdapterFactory extends CallAdapter.Factory { + + public static final Rxjava2SingleCallAdapterFactory INSTANCE = new Rxjava2SingleCallAdapterFactory(); + + @Override + public CallAdapter get(Type returnType, Annotation[] annotations, Retrofit retrofit) { + if (getRawType(returnType) != Single.class) { + return null; + } + if (!(returnType instanceof ParameterizedType)) { + throw new IllegalStateException( + "Mono return type must be parameterized" + + " as Single or Single"); + } + Type innerType = getParameterUpperBound(0, (ParameterizedType)returnType); + + if (getRawType(innerType) != Response.class) { + // Generic type is not Response. Use it for body-only adapter. + return new BodyCallAdapter<>(innerType); + } + + // Generic type is Response. Extract T and create the Response version of the adapter. + if (!(innerType instanceof ParameterizedType)) { + throw new IllegalStateException( + "Response must be parameterized" + " as Response or Response"); + } + Type responseType = getParameterUpperBound(0, (ParameterizedType)innerType); + return new ResponseCallAdapter<>(responseType); + } + + private class BodyCallAdapter implements CallAdapter> { + + private final Type responseType; + + public BodyCallAdapter(Type responseType) { + this.responseType = responseType; + } + + @Override + public Type responseType() { + return responseType; + } + + @Override + public Single adapt(Call call) { + + return Single.create(emitter -> call.enqueue(new BodyCallBack(emitter))); + } + + private class BodyCallBack implements Callback { + + private final SingleEmitter emitter; + + public BodyCallBack(@NonNull SingleEmitter emitter) { + this.emitter = emitter; + } + + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + emitter.onSuccess(response.body()); + } else { + emitter.onError(new HttpException(response)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + emitter.onError(t); + } + } + } + + private class ResponseCallAdapter implements CallAdapter>> { + + private final Type responseType; + + public ResponseCallAdapter(Type responseType) { + this.responseType = responseType; + } + + @Override + public Type responseType() { + return responseType; + } + + @Override + public Single> adapt(Call call) { + return Single.create(emitter -> call.enqueue(new ResponseCallBack(emitter))); + } + + private class ResponseCallBack implements Callback { + + private final SingleEmitter> emitter; + + public ResponseCallBack(@NonNull SingleEmitter> emitter) { + this.emitter = emitter; + } + + @Override + public void onResponse(Call call, Response response) { + emitter.onSuccess(response); + } + + @Override + public void onFailure(Call call, Throwable t) { + emitter.onError(t); + } + } + } +} diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/reactive/Rxjava3CompletableCallAdapterFactory.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/reactive/Rxjava3CompletableCallAdapterFactory.java new file mode 100644 index 0000000..d29b0a7 --- /dev/null +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/reactive/Rxjava3CompletableCallAdapterFactory.java @@ -0,0 +1,70 @@ +package com.github.lianjiatech.retrofit.spring.boot.core.reactive; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; + +import io.reactivex.rxjava3.annotations.NonNull; +import io.reactivex.rxjava3.core.Completable; +import io.reactivex.rxjava3.core.CompletableEmitter; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import retrofit2.Call; +import retrofit2.CallAdapter; +import retrofit2.Callback; +import retrofit2.HttpException; +import retrofit2.Response; +import retrofit2.Retrofit; + +/** + * @author 陈添明 + * @since 2022/6/10 8:08 上午 + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class Rxjava3CompletableCallAdapterFactory extends CallAdapter.Factory { + + public static final Rxjava3CompletableCallAdapterFactory INSTANCE = new Rxjava3CompletableCallAdapterFactory(); + + @Override + public CallAdapter get(Type returnType, Annotation[] annotations, Retrofit retrofit) { + if (getRawType(returnType) != Completable.class) { + return null; + } + return new NonBodyCallAdapter<>(); + } + + private static class NonBodyCallAdapter implements CallAdapter { + + @Override + public Type responseType() { + return Void.class; + } + + @Override + public Completable adapt(Call call) { + return Completable.create(emitter -> call.enqueue(new NonBodyCallBack(emitter))); + } + + private class NonBodyCallBack implements Callback { + + private final CompletableEmitter emitter; + + public NonBodyCallBack(@NonNull CompletableEmitter emitter) { + this.emitter = emitter; + } + + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + emitter.onComplete(); + } else { + emitter.onError(new HttpException(response)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + emitter.onError(t); + } + } + } +} diff --git a/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/reactive/Rxjava3SingleCallAdapterFactory.java b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/reactive/Rxjava3SingleCallAdapterFactory.java new file mode 100644 index 0000000..8555816 --- /dev/null +++ b/src/main/java/com/github/lianjiatech/retrofit/spring/boot/core/reactive/Rxjava3SingleCallAdapterFactory.java @@ -0,0 +1,134 @@ +package com.github.lianjiatech.retrofit.spring.boot.core.reactive; + +import java.lang.annotation.Annotation; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +import io.reactivex.rxjava3.annotations.NonNull; +import io.reactivex.rxjava3.core.Single; +import io.reactivex.rxjava3.core.SingleEmitter; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import retrofit2.Call; +import retrofit2.CallAdapter; +import retrofit2.Callback; +import retrofit2.HttpException; +import retrofit2.Response; +import retrofit2.Retrofit; + +/** + * @author 陈添明 + * @since 2022/6/10 8:08 上午 + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class Rxjava3SingleCallAdapterFactory extends CallAdapter.Factory { + + public static final Rxjava3SingleCallAdapterFactory INSTANCE = new Rxjava3SingleCallAdapterFactory(); + + @Override + public CallAdapter get(Type returnType, Annotation[] annotations, Retrofit retrofit) { + if (getRawType(returnType) != Single.class) { + return null; + } + if (!(returnType instanceof ParameterizedType)) { + throw new IllegalStateException( + "Mono return type must be parameterized" + + " as Single or Single"); + } + Type innerType = getParameterUpperBound(0, (ParameterizedType)returnType); + + if (getRawType(innerType) != Response.class) { + // Generic type is not Response. Use it for body-only adapter. + return new BodyCallAdapter<>(innerType); + } + + // Generic type is Response. Extract T and create the Response version of the adapter. + if (!(innerType instanceof ParameterizedType)) { + throw new IllegalStateException( + "Response must be parameterized" + " as Response or Response"); + } + Type responseType = getParameterUpperBound(0, (ParameterizedType)innerType); + return new ResponseCallAdapter<>(responseType); + } + + private class BodyCallAdapter implements CallAdapter> { + + private final Type responseType; + + public BodyCallAdapter(Type responseType) { + this.responseType = responseType; + } + + @Override + public Type responseType() { + return responseType; + } + + @Override + public Single adapt(Call call) { + + return Single.create(emitter -> call.enqueue(new BodyCallBack(emitter))); + } + + private class BodyCallBack implements Callback { + + private final SingleEmitter emitter; + + public BodyCallBack(@NonNull SingleEmitter emitter) { + this.emitter = emitter; + } + + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + emitter.onSuccess(response.body()); + } else { + emitter.onError(new HttpException(response)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + emitter.onError(t); + } + } + } + + private class ResponseCallAdapter implements CallAdapter>> { + + private final Type responseType; + + public ResponseCallAdapter(Type responseType) { + this.responseType = responseType; + } + + @Override + public Type responseType() { + return responseType; + } + + @Override + public Single> adapt(Call call) { + return Single.create(emitter -> call.enqueue(new ResponseCallBack(emitter))); + } + + private class ResponseCallBack implements Callback { + + private final SingleEmitter> emitter; + + public ResponseCallBack(@NonNull SingleEmitter> emitter) { + this.emitter = emitter; + } + + @Override + public void onResponse(Call call, Response response) { + emitter.onSuccess(response); + } + + @Override + public void onFailure(Call call, Throwable t) { + emitter.onError(t); + } + } + } +} diff --git a/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/http/HttpApi.java b/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/http/HttpApi.java index 0a8940a..d0b8cf3 100644 --- a/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/http/HttpApi.java +++ b/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/http/HttpApi.java @@ -12,6 +12,9 @@ import com.github.lianjiatech.retrofit.spring.boot.test.interceptor.TimeStamp2Interceptor; import com.github.lianjiatech.retrofit.spring.boot.test.interceptor.TimeStampInterceptor; +import io.reactivex.rxjava3.core.Completable; +import io.reactivex.rxjava3.core.Single; +import reactor.core.publisher.Mono; import retrofit2.Call; import retrofit2.Response; import retrofit2.http.Body; @@ -29,47 +32,59 @@ public interface HttpApi { /** - * 其他任意Java类型
- * 将响应体内容适配成一个对应的Java类型对象返回,如果http状态码不是2xx,直接抛错!
- * - * @param id id - * @return 其他任意Java类型 + * 基础类型(`String`/`Long`/`Integer`/`Boolean`/`Float`/`Double`):直接将响应内容转换为上述基础类型。 + */ + @POST("getString") + String getString(@Body Person person); + + /** + * 其它任意POJO类型: 将响应体内容适配成一个对应的POJO类型对象返回,如果http状态码不是2xx,直接抛错! */ @GET("person") Result getPerson(@Query("id") Long id); /** - * CompletableFuture
- * 将响应体内容适配成CompletableFuture对象返回,异步调用 - * - * @param id id - * @return CompletableFuture + * `CompletableFuture` :将响应体内容适配成CompletableFuture对象返回,异步调用 */ @GET("person") CompletableFuture> getPersonCompletableFuture(@Query("id") Long id); /** - * Response
- * 将响应内容适配成Response对象返回 - * - * @param id id - * @return Response . + * `Void`: 不关注返回类型可以使用`Void`,如果http状态码不是2xx,直接抛错! + */ + @POST("savePerson") + Void savePersonVoid(@Body Person person); + + /** + * `Response`:将响应内容适配成Response对象返回 */ @GET("person") Response> getPersonResponse(@Query("id") Long id); /** - * Call
- * 不执行适配处理,直接返回Call对象 - * - * @param id id - * @return Call实例 + * `Call`:不执行适配处理,直接返回Call对象 */ @GET("person") Call> getPersonCall(@Query("id") Long id); - @POST("savePerson") - Void savePersonVoid(@Body Person person); + + /** + * `Mono` : project-reactor响应式返回类型 + */ + @GET("person") + Mono> monoPerson(@Query("id") Long id); + + /** + * `Single`:rxjava响应式返回类型(支持rxjava2/rxjava3) + */ + @GET("person") + Single> singlePerson(@Query("id") Long id); + + /** + * `Completable`:rxjava响应式返回类型,http请求没有响应体(支持rxjava2/rxjava3) + */ + @GET("ping") + Completable ping(); @POST("savePerson") Result savePerson(@Body Person person); @@ -80,9 +95,6 @@ public interface HttpApi { @POST("savePersonList") Result savePersonList(@Body List personList); - @POST("getString") - String getString(@Body Person person); - @POST("getBoolean") Boolean getBoolean(@Body Person person); } diff --git a/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/completable/Rxjava2CompletableTest.java b/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/completable/Rxjava2CompletableTest.java new file mode 100644 index 0000000..84e056a --- /dev/null +++ b/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/completable/Rxjava2CompletableTest.java @@ -0,0 +1,65 @@ +package com.github.lianjiatech.retrofit.spring.boot.test.reactive.completable; + +import java.io.IOException; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import com.github.lianjiatech.retrofit.spring.boot.test.RetrofitTestApplication; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import io.reactivex.Completable; +import lombok.extern.slf4j.Slf4j; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; + +/** + * @author 陈添明 + */ +@SpringBootTest(classes = RetrofitTestApplication.class) +@RunWith(SpringRunner.class) +@Slf4j +public class Rxjava2CompletableTest { + + @Autowired + private Rxjava2CompletableTestApi rxjava2CompletableTestApi; + + private MockWebServer server; + + private static Gson gson = new GsonBuilder() + .create(); + + @Before + public void before() throws IOException { + System.out.println("=========开启MockWebServer==========="); + server = new MockWebServer(); + server.start(8080); + + } + + @After + public void after() throws IOException { + System.out.println("=========关闭MockWebServer==========="); + server.close(); + } + + + + @Test + public void ping() { + MockResponse response = new MockResponse() + .setResponseCode(200) + .addHeader("Cache-Control", "no-cache"); + + server.enqueue(response); + + Completable completable = rxjava2CompletableTestApi.ping(); + completable.blockingAwait(); + } +} diff --git a/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/completable/Rxjava2CompletableTestApi.java b/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/completable/Rxjava2CompletableTestApi.java new file mode 100644 index 0000000..64015b1 --- /dev/null +++ b/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/completable/Rxjava2CompletableTestApi.java @@ -0,0 +1,16 @@ +package com.github.lianjiatech.retrofit.spring.boot.test.reactive.completable; + +import com.github.lianjiatech.retrofit.spring.boot.core.RetrofitClient; + +import io.reactivex.Completable; +import retrofit2.http.GET; + +/** + * @author 陈添明 + */ +@RetrofitClient(baseUrl = "${test.baseUrl}") +public interface Rxjava2CompletableTestApi { + + @GET("ping") + Completable ping(); +} diff --git a/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/completable/Rxjava3CompletableTest.java b/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/completable/Rxjava3CompletableTest.java new file mode 100644 index 0000000..5414c33 --- /dev/null +++ b/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/completable/Rxjava3CompletableTest.java @@ -0,0 +1,65 @@ +package com.github.lianjiatech.retrofit.spring.boot.test.reactive.completable; + +import java.io.IOException; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import com.github.lianjiatech.retrofit.spring.boot.test.RetrofitTestApplication; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import io.reactivex.rxjava3.core.Completable; +import lombok.extern.slf4j.Slf4j; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; + +/** + * @author 陈添明 + */ +@SpringBootTest(classes = RetrofitTestApplication.class) +@RunWith(SpringRunner.class) +@Slf4j +public class Rxjava3CompletableTest { + + @Autowired + private Rxjava3CompletableTestApi rxjava3CompletableTestApi; + + private MockWebServer server; + + private static Gson gson = new GsonBuilder() + .create(); + + @Before + public void before() throws IOException { + System.out.println("=========开启MockWebServer==========="); + server = new MockWebServer(); + server.start(8080); + + } + + @After + public void after() throws IOException { + System.out.println("=========关闭MockWebServer==========="); + server.close(); + } + + + + @Test + public void ping() { + MockResponse response = new MockResponse() + .setResponseCode(200) + .addHeader("Cache-Control", "no-cache"); + + server.enqueue(response); + + Completable completable = rxjava3CompletableTestApi.ping(); + completable.blockingAwait(); + } +} diff --git a/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/completable/Rxjava3CompletableTestApi.java b/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/completable/Rxjava3CompletableTestApi.java new file mode 100644 index 0000000..38d0eb6 --- /dev/null +++ b/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/completable/Rxjava3CompletableTestApi.java @@ -0,0 +1,16 @@ +package com.github.lianjiatech.retrofit.spring.boot.test.reactive.completable; + +import com.github.lianjiatech.retrofit.spring.boot.core.RetrofitClient; + +import io.reactivex.rxjava3.core.Completable; +import retrofit2.http.GET; + +/** + * @author 陈添明 + */ +@RetrofitClient(baseUrl = "${test.baseUrl}") +public interface Rxjava3CompletableTestApi { + + @GET("ping") + Completable ping(); +} diff --git a/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/mono/MonoTest.java b/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/mono/MonoTest.java new file mode 100644 index 0000000..40a2e00 --- /dev/null +++ b/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/mono/MonoTest.java @@ -0,0 +1,126 @@ +package com.github.lianjiatech.retrofit.spring.boot.test.reactive.mono; + +import java.io.IOException; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import com.github.lianjiatech.retrofit.spring.boot.test.RetrofitTestApplication; +import com.github.lianjiatech.retrofit.spring.boot.test.entity.Person; +import com.github.lianjiatech.retrofit.spring.boot.test.entity.Result; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import lombok.extern.slf4j.Slf4j; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import reactor.core.publisher.Mono; +import retrofit2.Response; + +/** + * @author 陈添明 + */ +@SpringBootTest(classes = RetrofitTestApplication.class) +@RunWith(SpringRunner.class) +@Slf4j +public class MonoTest { + + @Autowired + private MonoTestApi monoTestApi; + + private MockWebServer server; + + private static Gson gson = new GsonBuilder() + .create(); + + @Before + public void before() throws IOException { + System.out.println("=========开启MockWebServer==========="); + server = new MockWebServer(); + server.start(8080); + + } + + @After + public void after() throws IOException { + System.out.println("=========关闭MockWebServer==========="); + server.close(); + } + + @Test + public void test() { + + // mock + Person mockPerson = new Person().setId(1L) + .setName("test") + .setAge(10); + Result mockResult = new Result<>() + .setCode(0) + .setMsg("ok") + .setData(mockPerson); + MockResponse response = new MockResponse() + .setResponseCode(200) + .addHeader("Content-Type", "application/json; charset=utf-8") + .addHeader("Cache-Control", "no-cache") + .setBody(gson.toJson(mockResult)); + server.enqueue(response); + + // http check + Mono> mono = monoTestApi.getPerson(1L); + log.info("=======Mono立即返回,但未执行调用======="); + Result person = mono.block(); + Person data = person.getData(); + Assert.assertNotNull(data); + Assert.assertEquals("test", data.getName()); + Assert.assertEquals(10, data.getAge().intValue()); + } + + @Test + public void testResponse() { + + // mock + Person mockPerson = new Person().setId(1L) + .setName("test") + .setAge(10); + Result mockResult = new Result<>() + .setCode(0) + .setMsg("ok") + .setData(mockPerson); + MockResponse response = new MockResponse() + .setResponseCode(200) + .addHeader("Content-Type", "application/json; charset=utf-8") + .addHeader("Cache-Control", "no-cache") + .setBody(gson.toJson(mockResult)); + server.enqueue(response); + + // http check + Mono>> personResponse = monoTestApi.getPersonResponse(1L); + log.info("=======Mono立即返回,但未执行调用======="); + Response> resultResponse = personResponse.block(); + Assert.assertEquals(200, resultResponse.code()); + Result person = resultResponse.body(); + Person data = person.getData(); + Assert.assertNotNull(data); + Assert.assertEquals("test", data.getName()); + Assert.assertEquals(10, data.getAge().intValue()); + } + + + @Test + public void ping() { + MockResponse response = new MockResponse() + .setResponseCode(200) + .addHeader("Cache-Control", "no-cache"); + + server.enqueue(response); + + Mono mono = monoTestApi.ping(); + mono.block(); + } +} diff --git a/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/mono/MonoTestApi.java b/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/mono/MonoTestApi.java new file mode 100644 index 0000000..4557f83 --- /dev/null +++ b/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/mono/MonoTestApi.java @@ -0,0 +1,26 @@ +package com.github.lianjiatech.retrofit.spring.boot.test.reactive.mono; + +import com.github.lianjiatech.retrofit.spring.boot.core.RetrofitClient; +import com.github.lianjiatech.retrofit.spring.boot.test.entity.Person; +import com.github.lianjiatech.retrofit.spring.boot.test.entity.Result; + +import reactor.core.publisher.Mono; +import retrofit2.Response; +import retrofit2.http.GET; +import retrofit2.http.Query; + +/** + * @author 陈添明 + */ +@RetrofitClient(baseUrl = "${test.baseUrl}") +public interface MonoTestApi { + + @GET("person") + Mono> getPerson(@Query("id") Long id); + + @GET("person") + Mono>> getPersonResponse(@Query("id") Long id); + + @GET("ping") + Mono ping(); +} diff --git a/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/single/Rxjava2SingleTest.java b/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/single/Rxjava2SingleTest.java new file mode 100644 index 0000000..b28bc9a --- /dev/null +++ b/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/single/Rxjava2SingleTest.java @@ -0,0 +1,113 @@ +package com.github.lianjiatech.retrofit.spring.boot.test.reactive.single; + +import java.io.IOException; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import com.github.lianjiatech.retrofit.spring.boot.test.RetrofitTestApplication; +import com.github.lianjiatech.retrofit.spring.boot.test.entity.Person; +import com.github.lianjiatech.retrofit.spring.boot.test.entity.Result; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import io.reactivex.Single; +import lombok.extern.slf4j.Slf4j; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import retrofit2.Response; + +/** + * @author 陈添明 + */ +@SpringBootTest(classes = RetrofitTestApplication.class) +@RunWith(SpringRunner.class) +@Slf4j +public class Rxjava2SingleTest { + + @Autowired + private Rxjava2SingleTestApi rxjava2SingleTestApi; + + private MockWebServer server; + + private static Gson gson = new GsonBuilder() + .create(); + + @Before + public void before() throws IOException { + System.out.println("=========开启MockWebServer==========="); + server = new MockWebServer(); + server.start(8080); + + } + + @After + public void after() throws IOException { + System.out.println("=========关闭MockWebServer==========="); + server.close(); + } + + @Test + public void test() { + + // mock + Person mockPerson = new Person().setId(1L) + .setName("test") + .setAge(10); + Result mockResult = new Result<>() + .setCode(0) + .setMsg("ok") + .setData(mockPerson); + MockResponse response = new MockResponse() + .setResponseCode(200) + .addHeader("Content-Type", "application/json; charset=utf-8") + .addHeader("Cache-Control", "no-cache") + .setBody(gson.toJson(mockResult)); + server.enqueue(response); + + // http check + Single> single = rxjava2SingleTestApi.getPerson(1L); + log.info("=======Single立即返回,但未执行调用======="); + Result person = single.blockingGet(); + Person data = person.getData(); + Assert.assertNotNull(data); + Assert.assertEquals("test", data.getName()); + Assert.assertEquals(10, data.getAge().intValue()); + } + + @Test + public void testResponse() { + + // mock + Person mockPerson = new Person().setId(1L) + .setName("test") + .setAge(10); + Result mockResult = new Result<>() + .setCode(0) + .setMsg("ok") + .setData(mockPerson); + MockResponse response = new MockResponse() + .setResponseCode(200) + .addHeader("Content-Type", "application/json; charset=utf-8") + .addHeader("Cache-Control", "no-cache") + .setBody(gson.toJson(mockResult)); + server.enqueue(response); + + // http check + Single>> personResponse = rxjava2SingleTestApi.getPersonResponse(1L); + log.info("=======Single立即返回,但未执行调用======="); + Response> resultResponse = personResponse.blockingGet(); + Assert.assertEquals(200, resultResponse.code()); + Result person = resultResponse.body(); + Person data = person.getData(); + Assert.assertNotNull(data); + Assert.assertEquals("test", data.getName()); + Assert.assertEquals(10, data.getAge().intValue()); + } +} diff --git a/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/single/Rxjava2SingleTestApi.java b/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/single/Rxjava2SingleTestApi.java new file mode 100644 index 0000000..d0ce5d4 --- /dev/null +++ b/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/single/Rxjava2SingleTestApi.java @@ -0,0 +1,23 @@ +package com.github.lianjiatech.retrofit.spring.boot.test.reactive.single; + +import com.github.lianjiatech.retrofit.spring.boot.core.RetrofitClient; +import com.github.lianjiatech.retrofit.spring.boot.test.entity.Person; +import com.github.lianjiatech.retrofit.spring.boot.test.entity.Result; + +import io.reactivex.Single; +import retrofit2.Response; +import retrofit2.http.GET; +import retrofit2.http.Query; + +/** + * @author 陈添明 + */ +@RetrofitClient(baseUrl = "${test.baseUrl}") +public interface Rxjava2SingleTestApi { + + @GET("person") + Single> getPerson(@Query("id") Long id); + + @GET("person") + Single>> getPersonResponse(@Query("id") Long id); +} diff --git a/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/single/Rxjava3SingleTest.java b/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/single/Rxjava3SingleTest.java new file mode 100644 index 0000000..5c4869e --- /dev/null +++ b/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/single/Rxjava3SingleTest.java @@ -0,0 +1,113 @@ +package com.github.lianjiatech.retrofit.spring.boot.test.reactive.single; + +import java.io.IOException; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import com.github.lianjiatech.retrofit.spring.boot.test.RetrofitTestApplication; +import com.github.lianjiatech.retrofit.spring.boot.test.entity.Person; +import com.github.lianjiatech.retrofit.spring.boot.test.entity.Result; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import io.reactivex.rxjava3.core.Single; +import lombok.extern.slf4j.Slf4j; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import retrofit2.Response; + +/** + * @author 陈添明 + */ +@SpringBootTest(classes = RetrofitTestApplication.class) +@RunWith(SpringRunner.class) +@Slf4j +public class Rxjava3SingleTest { + + @Autowired + private Rxjava3SingleTestApi rxjava3SingleTestApi; + + private MockWebServer server; + + private static Gson gson = new GsonBuilder() + .create(); + + @Before + public void before() throws IOException { + System.out.println("=========开启MockWebServer==========="); + server = new MockWebServer(); + server.start(8080); + + } + + @After + public void after() throws IOException { + System.out.println("=========关闭MockWebServer==========="); + server.close(); + } + + @Test + public void test() { + + // mock + Person mockPerson = new Person().setId(1L) + .setName("test") + .setAge(10); + Result mockResult = new Result<>() + .setCode(0) + .setMsg("ok") + .setData(mockPerson); + MockResponse response = new MockResponse() + .setResponseCode(200) + .addHeader("Content-Type", "application/json; charset=utf-8") + .addHeader("Cache-Control", "no-cache") + .setBody(gson.toJson(mockResult)); + server.enqueue(response); + + // http check + Single> single = rxjava3SingleTestApi.getPerson(1L); + log.info("=======Single立即返回,但未执行调用======="); + Result person = single.blockingGet(); + Person data = person.getData(); + Assert.assertNotNull(data); + Assert.assertEquals("test", data.getName()); + Assert.assertEquals(10, data.getAge().intValue()); + } + + @Test + public void testResponse() { + + // mock + Person mockPerson = new Person().setId(1L) + .setName("test") + .setAge(10); + Result mockResult = new Result<>() + .setCode(0) + .setMsg("ok") + .setData(mockPerson); + MockResponse response = new MockResponse() + .setResponseCode(200) + .addHeader("Content-Type", "application/json; charset=utf-8") + .addHeader("Cache-Control", "no-cache") + .setBody(gson.toJson(mockResult)); + server.enqueue(response); + + // http check + Single>> personResponse = rxjava3SingleTestApi.getPersonResponse(1L); + log.info("=======Single立即返回,但未执行调用======="); + Response> resultResponse = personResponse.blockingGet(); + Assert.assertEquals(200, resultResponse.code()); + Result person = resultResponse.body(); + Person data = person.getData(); + Assert.assertNotNull(data); + Assert.assertEquals("test", data.getName()); + Assert.assertEquals(10, data.getAge().intValue()); + } +} diff --git a/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/single/Rxjava3SingleTestApi.java b/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/single/Rxjava3SingleTestApi.java new file mode 100644 index 0000000..207d8e3 --- /dev/null +++ b/src/test/java/com/github/lianjiatech/retrofit/spring/boot/test/reactive/single/Rxjava3SingleTestApi.java @@ -0,0 +1,23 @@ +package com.github.lianjiatech.retrofit.spring.boot.test.reactive.single; + +import com.github.lianjiatech.retrofit.spring.boot.core.RetrofitClient; +import com.github.lianjiatech.retrofit.spring.boot.test.entity.Person; +import com.github.lianjiatech.retrofit.spring.boot.test.entity.Result; + +import io.reactivex.rxjava3.core.Single; +import retrofit2.Response; +import retrofit2.http.GET; +import retrofit2.http.Query; + +/** + * @author 陈添明 + */ +@RetrofitClient(baseUrl = "${test.baseUrl}") +public interface Rxjava3SingleTestApi { + + @GET("person") + Single> getPerson(@Query("id") Long id); + + @GET("person") + Single>> getPersonResponse(@Query("id") Long id); +} diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index da9e7b0..0483fa1 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -1,13 +1,10 @@ retrofit: - # 全局转换器工厂 + # 全局转换器工厂(已经内置了组件扩展的转换器工厂,这里请勿重复配置) global-converter-factories: - - com.github.lianjiatech.retrofit.spring.boot.core.BasicTypeConverterFactory - retrofit2.converter.jackson.JacksonConverterFactory - # 全局调用适配器工厂 + # 全局调用适配器工厂(已经内置了组件扩展的调用适配器工厂,这里请勿重复配置) global-call-adapter-factories: - - com.github.lianjiatech.retrofit.spring.boot.core.BodyCallAdapterFactory - - com.github.lianjiatech.retrofit.spring.boot.core.ResponseCallAdapterFactory # 全局日志打印配置 global-log: diff --git a/src/test/resources/default-config.yml b/src/test/resources/default-config.yml index 560202f..a7b3e09 100644 --- a/src/test/resources/default-config.yml +++ b/src/test/resources/default-config.yml @@ -1,12 +1,9 @@ retrofit: # 全局转换器工厂 global-converter-factories: - - com.github.lianjiatech.retrofit.spring.boot.core.BasicTypeConverterFactory - retrofit2.converter.jackson.JacksonConverterFactory # 全局调用适配器工厂 global-call-adapter-factories: - - com.github.lianjiatech.retrofit.spring.boot.core.BodyCallAdapterFactory - - com.github.lianjiatech.retrofit.spring.boot.core.ResponseCallAdapterFactory # 全局日志打印配置 global-log: