【Fegin技术专题】「原生态」打开Fegin之RPC技术的开端,你会使用原生态的Fegin吗?(高级用法)

语言: CN / TW / HK

theme: smartblue

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

对于Httpclient请求机制进行设置操作处理。

@Body请求体模板

@Body注解申明一个请求体模板,模板中可以带有参数,与方法中@Param注解申明的参数相匹配,使用方法如下:

java interface LoginClient { @RequestLine("POST /") @Headers("Content-Type: application/json") // json curly braces must be escaped! // 这里JSON格式需要的花括号居然需要转码,有点蛋疼了。 @Body("%7B\"user_name\": \"{user_name}\", \"password\": \"{password}\"%7D") void login(@Param("user_name") String user, @Param("password") String password); } ... client.login("denominator", "secret"); // {"user_name": "denominator", "password": "secret"}

Headers请求头

Feign支持给请求的api设置或者请求的客户端设置请求头,如下:

给API设置请求头

使用 @Headers 设置静态请求头

java // 给BaseApi中的所有方法设置Accept请求头 @Headers("Accept: application/json") interface BaseApi<V> { // 单独给put方法设置Content-Type请求头 @Headers("Content-Type: application/json") @RequestLine("PUT /api/{key}") void put(@Param("key") String, V value); }

设置动态值的请求头

java @RequestLine("POST /") @Headers("X-Ping: {token}") void post(@Param("token") String token);

设置key和value都是动态的请求头

调用时动态确定使用不同的请求头

可以使用 @HeaderMap 注解,如下:

java // @HeaderMap 注解设置的请求头优先于其他方式设置的 @RequestLine("POST /") void post(@HeaderMap Map<String, Object> headerMap);

给Target设置请求头

有时我们需要在一个API实现中根据不同的endpoint来传入不同的Header,这个时候我们可以使用自定义的RequestInterceptor 或 Target来实现.

通过自定义的 RequestInterceptor 来实现请查看 Request Interceptors

下面是一个通过自定义Target来实现给每个Target设置安全校验信息Header的例子:

java static class DynamicAuthTokenTarget<T> implements Target<T> { public DynamicAuthTokenTarget(Class<T> clazz, UrlAndTokenProvider provider, ThreadLocal<String> requestIdProvider); ... @Override public Request apply(RequestTemplate input) { TokenIdAndPublicURL urlAndToken = provider.get(); if (input.url().indexOf("http") != 0) { input.insert(0, urlAndToken.publicURL); } input.header("X-Auth-Token", urlAndToken.tokenId); input.header("X-Request-ID", requestIdProvider.get()); return input.request(); } } ... Bank bank = Feign.builder() .target(new DynamicAuthTokenTarget(Bank.class, provider, requestIdProvider));

  • 这种方法的实现依赖于给Feign 客户端设置的自定义的RequestInterceptor 或 Target。可以被用来给一个客户端的所有api请求设置请求头。比如说可是被用来在header中设置身份校验信息。这些方法是在线程执行api请求的时候才会执行,所以是允许在运行时根据上下文来动态设置header的。

  • 比如说可以根据线程本地存储(thread-local storage)来为不同的线程设置不同的请求头。

Base APIS

有些请求中的一些方法是通用的,但是可能会有不同的参数类型或者返回类型,这个时候可以这么用:

```java // 通用API interface BaseAPI { @RequestLine("GET /health") String health(); @RequestLine("GET /all") List all(); } // 继承通用API interface CustomAPI extends BaseAPI { @RequestLine("GET /custom") String custom(); } // 各种类型有相同的表现形式,定义一个统一的API @Headers("Accept: application/json") interface BaseApi { @RequestLine("GET /api/{key}") V get(@Param("key") String key); @RequestLine("GET /api") List list(); @Headers("Content-Type: application/json") @RequestLine("PUT /api/{key}") void put(@Param("key") String key, V value); } // 根据不同的类型来继承 interface FooApi extends BaseApi { } interface BarApi extends BaseApi { }

```

Logging

你可以通过设置一个 Logger 来记录http消息,如下:

java GitHub github = Feign.builder() decoder(new GsonDecoder()) .logger(new Logger.JavaLogger().appendToFile("logs/http.log")) .logLevel(Logger.Level.FULL) .target(GitHub.class, https://api.github.com);

Request Interceptors

当你希望修改所有的的请求的时候,你可以使用Request Interceptors。比如说,你作为一个中介,你可能需要为每个请求设置 X-Forwarded-For

java static class ForwardedForInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { template.header("X-Forwarded-For", "origin.host.com"); } } ... Bank bank = Feign.builder() .decoder(accountDecoder) .requestInterceptor(new ForwardedForInterceptor()) .target(Bank.class, https://api.examplebank.com);

或者,你可能需要实现Basic Auth,这里有一个内置的基础校验拦截器

java BasicAuthRequestInterceptor Bank bank = Feign.builder() .decoder(accountDecoder) .requestInterceptor(new BasicAuthRequestInterceptor(username, password)) .target(Bank.class, https://api.examplebank.com);

@Param Expansion

@Param 注解给模板中的参数设值的时候,默认的是使用的对象的 toString() 方法的值,通过声明 自定义的Param.Expander,用户可以控制其行为,比如说格式化 Date 类型的值:

java // 通过设置 @Param 的 expander 为 DateToMillis.class 可以定义Date类型的值 @RequestLine("GET /?since={date}") Result list(@Param(value = "date", expander = DateToMillis.class) Date date);

Dynamic Query Parameters

动态查询参数支持,通过使用 @QueryMap 可以允许动态传入请求参数,如下:

java @RequestLine("GET /find") V find(@QueryMap Map<String, Object> queryMap);

自定义注解扫描动态生成客户端

原生Feign只能一次解析一个接口,生成对应的请求代理对象,如果一个包里有多个调用接口就要多次解析非常麻烦。

扩展BeanFactoryPostProcessor接口、

自定义注解:在扫描接口的过程中,可以通过一个自定义注解,来区分Feign接口并且指定调用的服务Url

实现扩展容器

java @Component public class FeignClientRegister implements BeanFactoryPostProcessor{ @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { List<String> classes = scan(scanPath); if(classes==null){ return ; } Feign.Builder builder = getFeignBuilder(); if(classes.size()>0){ for (String claz : classes) { Class<?> targetClass = null; try { targetClass = Class.forName(claz); String url=targetClass.getAnnotation(FeignApi.class).serviceUrl(); if(url.indexOf("http://")!=0){ url="http://"+url; } Object target = builder.target(targetClass, url); beanFactory.registerSingleton(targetClass.getName(), target); } catch (Exception e) { throw new RuntimeException(e.getMessage()); } } } } public Feign.Builder getFeignBuilder(){ Feign.Builder builder = Feign.builder() .encoder(new JacksonEncoder()) .decoder(new JacksonDecoder()) .options(new Request.Options(1000, 3500)) .retryer(new Retryer.Default(5000, 5000, 3)); return builder; } public List<String> scan(String path){ ScanResult result = new FastClasspathScanner(path).matchClassesWithAnnotation(FeignApi.class, (Class<?> aClass) -> { }).scan(); if(result!=null){ return result.getNamesOfAllInterfaceClasses(); } return null; } }

「其他文章」