使用原生的Feign进行JSON的RPC调用

概述: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请求,获取我们需要的数据。

Fantastic Soft

风铃之书是个人的工作和生活的总结和分享的站点,欢迎来访和留言,有时也会提供自家软件的发布版本和开源项目。