概述:Feign
Feign 是一个应用广泛的框架,在spring-cloud中就有很多的使用,但是这里我想要讨论的不是如何在Spring环境中的使用,因为这种在Spring内部的使用方法已经有很多的例子了。
Feign框架使用动态代理的方法把java的接口包装为标准的Http调用,调用接口的方法等同于发送Http请求,远程的Http服务器返回的数据也能够通过Feign直接转换为在java中对应的Java对象。
那么如何在非Spring的环境中使用Feign进行Json的RPC调用呢?
通过Maven使用Feign
我们只需要一个Feign-Core和Feign-Jackson即可,如果需要携带JWT之类的Token,那么需要一个拦截器,这个可以使用Feign + OKHttp,因为OKHttp携带一个拦截器,在这里面就可以附加一个Token了。
Feign的Maven坐标:
<!-- Maven坐标开始 -->
<!-- https://mvnrepository.com/artifact/io.github.openfeign/feign-core -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
<version>11.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.github.openfeign/feign-jackson -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-jackson</artifactId>
<version>11.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.github.openfeign/feign-okhttp -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
<version>11.6</version>
</dependency>
<!-- Maven坐标结束 -->这些就是Feign需要的依赖了。
使用Feign映射Http接口
查看我们需要使用的Http的接口,新建一些Java类,这些类的结构与接口返回的JSON对象,或者需要通过接口发送的JSON对象的结构一致。
然后添加一个toString方法,里面使用Jackson将本对象写为Json字符串。
例如,Http接口会返回了如下json对象:
{
"id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"name": "My Project",
"owner_id": "dddddddd-dddd-dddd-dddd-dddddddddddd",
"private": false,
"project_preferences": {},
"root_asset_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
"team_id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"
}那么,根据上述的json,就需要定义以下java类:
// DTO
public class APIProject {
private String id;
private String name;
@JsonProperty("owner_id")
private String ownerId;
@JsonProperty("private")
private Boolean isPrivate;
@JsonProperty("root_asset_id")
private String rootAssetsId;
@JsonProperty("team_id")
private String teamId;
@JsonProperty("project_preferences")
private Map<String,String> projectPreferences;
// 省略Getter和Setter
@Override
public String toString() {
return new ObjectMapper().readTree(jsonString).toPrettyString();
}
}接下来,需要定义一个接口,这个接口会被Feign框架代理,当这个接口的方法被调用的时候,
就等同于发送一个Http请求,远程的Http服务根据请求来返回接口需要的内容。
// Feign 接口
public interface DemoFeignService {
// 这个是GET和DELETE的示例。
/* RequestLine注解的值是想要调用的Http的API的地址了,
* 要注意的是,这里不要加host
* 例如目标API是 https://demo.com/v2/test
* 那么这里应该写为: /v2/test,前面的host会在其他位置传进来。
*/
/* URL内部的 { } 里面的是变量名,他和外面的括号,会在使用的时候被
参数中标有Param注解的,且注解的注解的value和括号内的名字一致的那个参数替换掉。
*/
// 例如我们这样调用下面的这个接口:getTeamProjects("test-test-test-test"),第一个参数是写在
// RequestLine注解中的参数 team_id ,也就是{team_id},
// 那么这个RequestLine 里面的 {team_id} 将会替换为真实的参数,也就是 “test-test-test-test”,
// 所以,发送请求的真实的url是:"/v2/teams/test-test-test-test "
@RequestLine("GET /v2/teams/{team_id}/projects")
List<APIProject> getTeamProjects(@Param("team_id") String teamId);
/* 这个是POST,PUT的示例,
* 添加Body注解,body注解的value应该是一个字符串,这就是上面重写toString的目的。
* 重写的toString方法可以让传入的Java对象在被Feign调用的时候,自动的转换为JSON字符串。
* Header注解就是Http的Header,如果需要其他Header在这里配置就好。
* requestLine看上面一个函数的解释吧。
*/
@Body("{body}")
@Headers("Content-Type: application/json")
@RequestLine("POST /v2/teams/{teamId}/projects")
APIProject createProject(@Param("body") ProjectCreation creation, @Param("teamId") teamid);
}使用默认的HttpClient
有了上面这样一个接口,接下来就要通过Http的拦截器给请求添加一个Token,当然如果不需要token,这个也可以不做。
public class APIAuthInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
String token = "token的具体内容";
if (token == null) {
throw new RuntimeException("token can not be null");
}
// 添加token到Template上面
template.header("Authorization",token);
}
}这个拦截器是Feign自带的拦截器,添加Feign后就能直接使用它,如果需要在发送请求之前做些什么,这里就是最合适的地方了。
那么最后一步,通过Feign完成接口的代理,通过这个代理就能使用此接口调用远程的Http服务了。
// 构建Feign 代理对象
public static synchronized <T> T getAPIServices(Class<T> clazz) {
// 通过Feign代理参数中提供的接口类,生成接口对象用来执行http的RPC。
T result = Feign.builder()
// Request的超时设置。
.options(new Request.Options(3, TimeUnit.SECONDS, 30,TimeUnit.SECONDS,true))
// 重试器,在出现IO异常的时候Feign通过这个进行重试。
.retryer(new Retryer.Default(1000,3000,10))
// Jackson的Encoder和Decoder。
.encoder(new JacksonEncoder())
.decoder(new JacksonDecoder())
// 使用Feign的Logger。
.logger(new feign.Logger.ErrorLogger())
// 使用之前做好的拦截器
.requestInterceptor(new APIAuthInterceptor())
// 指定Feign代理的API的Host,以及被代理的API接口类。
.target(clazz,"API的host,例如这里填写https://demo.com");
return result;
}到了这里,Feign代理的Http接口就能使用了,只需要通过getAPIService(DemoFeignService.class),类似这样就能获取一个Feign的远程接口,就这样直接调用获取到的Service,就能通过发送http请求来获取需要的数据了。
其实到这里就已经可以结束了,但是如果需要使用其他的HttpClient,例如okhttp,那么请继续看下面的吧。
使用OkHttp作为Feign的Client
有了一个接口,接下来要做的是通过Http的拦截器给请求添加一个Token,如果不需要Token,那么这一步可以不做。
public class APIAuthInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
// 打印请求的具体内容,输出为log方便调试。
// 实际上如果需要在请求发出之前做一点什么,这里就是最好的地方。
// 例如上面说的添加认证的token。
Buffer buffer = new Buffer();
if (request.body() != null) {
// 打印请求的body
request.body().writeTo(buffer);
String content = new String(buffer.readByteArray());
LOGGER.info("Request " + request.url() + "\n" + content);
} else {
// 请求没有body,打印它的url。
LOGGER.info("Request " + request.url());
}
request = request.newBuilder()
.addHeader("Authorization", "Token的具体内容。")
.build();
return chain.proceed(request);
}
}那么,现在就到了最后一步,使用Feign生成接口的代理,通过这个代理就能够完成JSON RPC的调用了。
// 以OkHttp为基础构建代理
public static synchronized <T> T getAPIServices(Class<T> clazz) {
// 创建OKHttp的Client,这个是OKHttp提供的API。
// 利用上面的APIAuthInterceptor拦截器拦截请求。
okHttp3.OkHttpClient okClient = okClient = new okhttp3.OkHttpClient
.Builder()
.addInterceptor(new APIAuthInterceptor())
.build();
// Feign的HTTPClient,通过上面的OKHttpClient 构建。
OkHttpClient client = new OkHttpClient(okClient);
// 通过Feign代理参数中提供的接口类,生成接口对象用来执行http的RPC。
T result = Feign.builder()
// 使用上述的Client。
.client(client)
// Request的超时设置。
.options(new Request.Options(3, TimeUnit.SECONDS, 30,TimeUnit.SECONDS,true))
// 重试器,在出现IO异常的时候Feign通过这个进行重试。
.retryer(new Retryer.Default(1000,3000,10))
// Jackson的Encoder和Decoder。
.encoder(new JacksonEncoder())
.decoder(new JacksonDecoder())
// 使用Feign的Logger。
.logger(new feign.Logger.ErrorLogger())
// 指定Feign代理的API的Host,以及被代理的API接口类。
.target(clazz,"API的host,例如这里填写https://demo.com");
return result;
}到此为止,Feign就以及接口就可以使用了,只需要通过 getAPIServices(DemoFeignService.class)就能够得到一个代理对象,
直接调用代理对象的方法,就可以像指定的服务器发送Http请求,获取我们需要的数据。




