声明式
概述
声明式调用是面向接口,可以通过接口方法声明绑定相应的注解达到方法调用即是HTTP
调用的效果。见下面这个简单的例子:
// HttpBinApi API接口定义
public interface HttpBinApi {
@Get(path = "/get")
String getString(@Query("foo") String fooVal);
}
ApiEnhancer apiEnhancer = Solpic.newApiEnhancerBuilder()
.httpClient(Solpic.newHttpClientBuilder().type(HttpClientType.JDK_HTTPCLIENT).build())
.baseUrl("https://httpbin.org")
.build();
HttpBinApi httpBinApi = apiEnhancer.enhance(HttpBinApi.class);
String response = httpBinApi.getString("bar");
System.out.println(response);
上面的声明式调用效果和下面这个命令式调用的例子是一致的:
HttpClient httpClient = Solpic.newHttpClientBuilder().type(HttpClientType.JDK_HTTPCLIENT).build();
HttpRequest httpRequest = HttpRequest.newBuilder()
.uri(URI.create("https://httpbin.org/get"))
.method(HttpMethod.GET)
.query("foo", "bar")
.build();
String content = httpClient.sendSimple(httpRequest, PayloadSubscribers.X.ofString());
System.out.println(content);
注解支持
Solpic
提供大量注解用于支持API
接口方法和实际的HTTP
调用参数。
@Request
@Request
对于指定请求方法、请求的(相对)路径和请求的绝对路径。方法参数如下:
method()
:用于指定HTTP
请求方法,例如HttpMethod.GET
path()
:用于指定HTTP
请求的路径,默认值为空字符串url()
:用于指定HTTP
请求的绝对URL
,此值有较高优先级并且会覆盖path()
的值,默认值为空字符串
当path()
和url()
都为空字符串的时候,@Request
注解所在的方法名称会被作为HTTP
请求的路径使用。@Request
的同类型特化注解有@Get
、@Post
、@Put
、@Delete
、@Options
、@Head
、@Patch
和@Trace
。举个例子:
public interface HttpBinApi {
@Get(path = "/get")
String getString1(@Query("foo") String fooVal);
@Request(path = "/get", method = HttpMethod.GET)
String getString2(@Query("foo") String fooVal);
}
@Query
@Query
用于指定单个HTTP
请求的URL
参数,其中encoded()
属性用于指定参数的名称和值是否需要进行URL
编码,默认为false
。
@Queries
@Queries
用于指定多个HTTP
请求的URL
参数,其中encoded()
属性用于指定参数的名称和值是否需要进行URL
编码,默认为false
。此注解所在参数的类型可以是Map
或者Iterable<Pair>
。例如:
public interface HttpBinApi {
@Get(path = "/get")
String getString1(@Queries Map<String, String> qs);
@Get(path = "/get")
String getString2(@Queries List<Pair> qs);
}
这里的Pair
的全类名为cn.vlts.solpic.core.util.Pair
。
@Consume
@Consume
用于指定HTTP
请求的Content-Type
,优先级低于请求头参数。例如:
@Consume("application/json")
public interface HttpBinApi {
}
@Produce
@Produce
用于指定HTTP
响应的Content-Type
,目前只用于JSON
的反序列化策略匹配和判断。
@Header
@Header
用于指定单个请求头K-V
参数。例如:
public interface HttpBinApi {
@Get(path = "/get")
String getString(@Query("foo") String fooVal, @Header("k") String headerVal);
}
@Headers
@Headers
用于指定多个请求头K-V
参数,此注解所在参数的类型可以是Map
或者Iterable<HttpHeader>
。例如:
public interface HttpBinApi {
@Get(path = "/get")
String getString1(@Query("foo") String fooVal, @Headers Map<String, String> headers);
@Get(path = "/get")
String getString2(@Query("foo") String fooVal, @Headers List<HttpHeader> headers);
}
这里的HttpHeader
的全类名为cn.vlts.solpic.core.http.HttpHeader
。
@Form
@Form
对应于请求Content-Type
为application/x-www-form-urlencoded
的请求有效载荷的特化场景处理,此注解所在参数的类型可以是Map
或者UrlEncodedForm
。例如:
public interface HttpBinApi {
@Post(path = "/post")
String form1(@Form Map<String, String> form);
@Post(path = "/post")
String form2(@Form UrlEncodedForm form);
}
@Multipart
@Multipart
对应于请求Content-Type
为multipart/form-data
的请求有效载荷的特化场景处理,此注解所在参数的类型必须是MultipartData
。例如:
public interface HttpBinApi {
@Post(path = "/post")
String multipart(@Multipart MultipartData m);
}
@Opt
@Opt
用于指定HTTP
请求的选项配置,只支持REQUEST
级别的选项。例如:
public interface HttpBinApi {
@Get(path = "/get")
@Opt(key = "HTTP_ENABLE_EXECUTE_TRACING", value = "true")
@Opt(key = "HTTP_ENABLE_LOGGING", value = "true")
String getString(@Query("foo") String fooVal);
}
@Var
@Var
用于配置某些特殊请求类型所需的参数,目前支持三种参数delay
、listener
和promise
,下文遇到具体使用场景时候再详细说明。
@Payload
@Payload
用于指定请求有效载荷(请求体),也就是所有非特化场景类型的请求有效载荷都是用此注解处理对应的参数。例如:
public interface HttpBinApi {
@Post(path = "/post")
@Consume("text/plain")
String post(@Payload String content);
}
更详细的说明见下面的"参数转换"小节。
创建服务
首先要初始化ApiEnhancer
实例:
ApiEnhancer apiEnhancer = Solpic.newApiEnhancerBuilder()
.httpClient(Solpic.newHttpClientBuilder().type(HttpClientType.JDK_HTTPCLIENT).build()) // 指定HttpClient
.baseUrl("https://httpbin.org") // 指定基础请求URL
.delay(1000L) // 默认的延迟调度发送时间,单位为毫秒 - 用于scheduledSend场景
.promise(CompletableFuture::new) // 默认的Promise提供者 - 用于scheduledSend场景
.listener(() -> (f) -> {}) // 默认的FutureListener提供者 - 用于enqueue场景
.loadEagerly() // 是否提前加载解析所有方法
.codec(new JacksonCodec<>()) // 指定编码解码器
.addConverterFactory(new ConverterFactory<>() {}) // 添加转换工厂
.build();
得到ApiEnhancer
实例后,基于它和指定API
接口类型即可创建API
接口实例:
public interface HttpBinApi {
@Get(path = "/get")
String getString(@Query("foo") String fooVal);
}
HttpBinApi httpBinApi = apiEnhancer.enhance(HttpBinApi.class);
接着可以直接调用API
接口实例的方法实现HTTP
调用。
参数转换
ApiEnhancer
增强的API
接口代理实例很多时候需要进行入参和返回值转换,此功能由转换工厂ConverterFactory
完成。自定义参数转换实现只需要继承ConverterFactory
编写转换逻辑,然后把转换工厂实例添加到ApiEnhancer
中。这里举个请求参数和响应结果都是嵌套泛型的例子:
@Data
public class CommonRequest<T> {
private String requestId;
private Long timestamp;
private T data;
}
@Data
public class CommonResponse<T> {
private String requestId;
private Long code;
private String message;
private T data;
}
@Data
public class CommonData {
private String name;
private Long amount;
}
public interface CommonApi {
@Post(path = "/invoke")
CommonResponse<CommonData> invoke(@Payload CommonRequest<CommonData> request);
}
编写CommonApi
的参数转换工厂CommonApiConverterFactory
如下:
class CommonApiConverterFactory extends ConverterFactory<CommonRequest, CommonResponse> {
private static final String INVOKE_METHOD = "invoke";
private final Codec<CommonRequest, CommonResponse> codec = new JacksonCodec<>();
@Override
public boolean supportRequestConverter(ApiParameterMetadata metadata) {
Class<?> rawType = ReflectionUtils.X.getRawType(metadata.getParameterType());
return CommonRequest.class.isAssignableFrom(rawType);
}
@Override
public Converter<CommonRequest, RequestPayloadSupport> newRequestConverter(ApiParameterMetadata metadata) {
if (Objects.equals(INVOKE_METHOD, metadata.getMethodName())) {
return codec::createPayloadPublisher;
}
return super.newRequestConverter(metadata);
}
@Override
public boolean supportResponseSupplier(ApiParameterMetadata metadata) {
Class<?> rawType = ReflectionUtils.X.getRawType(metadata.getReturnType());
return CommonResponse.class.isAssignableFrom(rawType);
}
@Override
public Supplier<ResponsePayloadSupport<CommonResponse>> newResponseSupplier(ApiParameterMetadata metadata) {
if (Objects.equals(INVOKE_METHOD, metadata.getMethodName())) {
return () -> codec.createPayloadSubscriber(metadata.getReturnType());
}
return super.newResponseSupplier(metadata);
}
}
最后通过ApiEnhancerBuilder
加载CommonApiConverterFactory
使用即可:
CommonApi commonApi = Solpic.newApiEnhancerBuilder()
.addConverterFactory(new CommonApiConverterFactory())
.build()
.enhance(CommonApi.class);
通过ApiEnhancer
进行代理增强的API
接口,不仅仅支持同步发送HTTP
请求,还支持异步发送、队列发送和调度发送,并且方法返回结果可以使用复杂的泛型嵌套类型。这里提供一个例子,列举目前Solpic
支持的所有复杂声明式API
定义:
public interface HttpBinApi {
@Get(path = "/get")
HttpResponse<?> p1(@Query(value = "foo") String bar);
@Get(path = "/get")
HttpResponse<Map<String, Object>> p2(@Query(value = "foo") String bar);
@Get(path = "/get")
String p3(@Query(value = "foo") String bar);
@Get(path = "/get")
HttpBinResult p4(@Query(value = "foo") String bar);
@Get(path = "/get")
HttpResponse<HttpBinResult> p5(@Query(value = "foo") String bar);
@Get(path = "/get")
CompletableFuture<HttpBinResult> p6(@Query(value = "foo") String bar);
@Get(path = "/get")
CompletableFuture<HttpResponse<HttpBinResult>> p7(@Query(value = "foo") String bar);
@Get(path = "/get")
ListenableFuture<?> p8(@Query(value = "foo") String bar,
@Var("listener") FutureListener<?> listener);
@Get(path = "/get")
ListenableFuture<HttpBinResult> p9(@Query(value = "foo") String bar,
@Var("listener") FutureListener<HttpBinResult> listener);
@Get(path = "/get")
ListenableFuture<HttpResponse<HttpBinResult>> p10(@Query(value = "foo") String bar,
@Var("listener") FutureListener<HttpResponse<HttpBinResult>> listener);
@Get(path = "/get")
ScheduledFuture<?> p11(@Query(value = "foo") String bar,
@Var("delay") Long delay,
@Var("promise") CompletableFuture<?> promise);
@Get(path = "/get")
ScheduledFuture<HttpBinResult> p12(@Query(value = "foo") String bar,
@Var("delay") Long delay,
@Var("promise") CompletableFuture<HttpBinResult> promise);
@Get(path = "/get")
ScheduledFuture<HttpResponse<HttpBinResult>> p13(@Query(value = "foo") String bar,
@Var("delay") Long delay,
@Var("promise") CompletableFuture<HttpResponse<HttpBinResult>> promise);
}
其中:
p1 ~ p5
为同步发送模式,对应于HttpClient#send()
和HttpClient#sendSimple()
方法p6 ~ p7
为异步发送模式,对应于HttpClient#sendAsync()
和HttpClient#sendAsyncSimple()
方法p8 ~ p10
为队列发送模式,对应于HttpClient#enqueue()
和HttpClient#enqueueSimple()
方法p11 ~ p13
为调度发送模式,对应于HttpClient#scheduledSend()
和HttpClient#scheduledSendSimple()
方法
对于队列发送模式,需要配置结果监听器FutureListener
实例,添加结果监听器必须使用@Var("listener")
注解声明。对于调度发送模式,需要配置延迟发送时间(单位为毫秒)和原始类型为CompletableFuture
的泛型Promise
,前者必须使用@Var("delay")
注解声明,后者必须使用@Var("promise")
注解声明。ApiEnhancer
在处理这四种发送模式的时候已经基于返回值类型做了不同的调用分支,但是鉴于提取泛型参数的逻辑比较复杂,返回值泛型嵌套类型最里面的真实类型需要在转换器工厂中指定,以上面的HttpBinApi
为例:
@SuppressWarnings("rawtypes")
public class HttpBinConverterFactory extends ConverterFactory {
final Codec codec = new JacksonCodec();
@Override
public boolean supportResponseSupplier(ApiParameterMetadata metadata) {
return true;
}
@Override
public Supplier<ResponsePayloadSupport> newResponseSupplier(ApiParameterMetadata metadata) {
ReflectionUtils.ParameterizedTypeInfo pti = ReflectionUtils.X.getParameterizedTypeInfo(metadata.getReturnType());
// 提取最里层参数位置为0的参数类型,也就是HttpBinResult.class或者NULL
Class<?> rawType = pti.getRawClass(pti.getMaxDepth(), 0);
if (HttpBinResult.class.isAssignableFrom(rawType)) {
return () -> codec.createPayloadSubscriber(rawType);
}
return super.newResponseSupplier(metadata);
}
}
对于使用@Payload
注解声明的方法入参,可以使用参数转换工厂的ConverterFactory#supportRequestConverter()
和ConverterFactory#newRequestConverter()
判断是否需要生成转换器和创建转换器实例。例如:
@SuppressWarnings({"rawtypes", "unchecked"})
class CustomConverterFactory extends ConverterFactory {
private static final String M = "someMethod";
private final Codec codec = new JacksonCodec<>();
@Override
public boolean supportRequestConverter(ApiParameterMetadata metadata) {
Class<?> rawType = ReflectionUtils.X.getRawType(metadata.getParameterType());
if (Map.class.isAssignableFrom(rawType) ||
HttpBinResult.class.isAssignableFrom(rawType) ||
Objects.equals(M, metadata.getMethodName())) {
return true;
}
return false;
}
@Override
public Converter<?, RequestPayloadSupport> newRequestConverter(ApiParameterMetadata metadata) {
return codec::createPayloadPublisher;
}
}
编码解码器Codec
通常能处理绝大部分请求有效载荷为JSON
对象的场景。