引言
Feign是一个声明式REST客户端,它能让REST调用更加简单。Feign提供了HTTP请求的模板,通过编写简单的接口和插入注解,就可以定义好HTTP请求的参数、格式、地址等信息。
而Feign则会完全代理HTTP请求,我们只需要像调用方法一样调用它就可以完成服务请求及相关处理。 Spring Cloud对 Feign进行了封装,使其支持 Springmvc标准注解和Httpmessage Converters Feign可以与 Eureka和 Ribbon组合使用以支持负载均衡。
——《spring cloud 微服务 入门、进阶与实战》第70页
在spring cloud 中集成Feign
1 2 3 4 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-openfeign</artifactId > </dependency >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import org.springframework.cloud.openfeign.FeignClient;import org.springframework.web.bind.annotation.GetMapping;@FeignClient(value = "eureka-client-service") public interface UserRemoteClient { @GetMapping("/user/hello") String hello () ; }
1 2 3 4 5 6 7 8 @EnableFeignClients(basePackages = "com.study.feignInfo") @SpringBootApplication public class SpringcloudFeignApplication { public static void main (String[] args) { SpringApplication.run(SpringcloudFeignApplication.class, args); } }
1 2 3 4 5 6 7 8 9 10 @RestController public class UserController { @Autowired private UserRemoteClient userRemoteClient; @GetMapping("/call/hello") public String cellHello () { return userRemoteClient.hello(); } }
启动项目访问 /call/hello 成功返回了 eureka-client-service 服务的数据
自定义Feign配置 日志配置
1 2 3 4 5 6 7 8 9 10 11 12 import feign.Logger;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configuration public class FeignConfiguration { @Bean Logger.Level feignLoggerLevel () { return Logger.Level.FULL; } }
在 UserRemoteClient @FeignClient注解加入配置
1 @FeignClient(value = "eureka-client-service" ,configuration = FeignConfiguration.class)
1 2 logging.level.com.study.feignInfo.UserRemoteClient =DEBUG
再次调用测试 /call/hello 接口,控制台日志输出:
1 2 3 4 5 6 7 8 9 [UserRemoteClient#hello] <--- HTTP/1.1 200 (33ms) [UserRemoteClient#hello] connection: keep-alive [UserRemoteClient#hello] content-length: 5 [UserRemoteClient#hello] content-type: text/plain;charset=UTF-8 [UserRemoteClient#hello] date: Sat, 02 May 2020 15:41:22 GMT [UserRemoteClient#hello] keep-alive: timeout=60 [UserRemoteClient#hello] [UserRemoteClient#hello] hello [UserRemoteClient#hello] <--- END HTTP (5-byte body)
契约配置 spring cloud 在 Feign 基础上做了扩展,使其能够支持 spring mvc 的注解。原生的 Feign 是不支持 spring mvc 的注解的。如果想用原生的方式来定义 Feign 客户端,可通过如下配置:
1 2 3 4 5 6 7 8 @Configuration public class FeignConfiguration { @Bean public Contract feignContract () { return new feign .Contract.Default(); } }
当这样配置后,如果前面定义客户端的代码不改变,项目重启时会报错。
1 org.springframework .beans .factory .UnsatisfiedDependencyException : Error creating bean with name 'userController': Unsatisfied dependency expressed through field 'userRemoteClient'; nested exception is org.springframework .beans .factory .BeanCreationException : Error creating bean with name 'com.study .feignInfo .UserRemoteClient ': FactoryBean threw exception on object creation; nested exception is java.lang .IllegalStateException : Method UserRemoteClient#hello() not annotated with HTTP method type (ex. GET, POST)
Basic 认证配置 如果服务端开启了 Basic 认证。我们可以通过下面方式配置登录名和密码
1 2 3 4 5 6 7 8 @Configuration public class FeignConfiguration { @Bean public BasicAuthRequestInterceptor basicAuthRequestInterceptor () { return new BasicAuthRequestInterceptor ("WuZhiYong" , "123456" ); } }
如果服务端使用的其它的认证方式,我们可以配置自定义请求拦截器。把相应的认证信息存入head 中
1 2 3 4 5 6 7 8 9 10 11 import feign.RequestInterceptor;import feign.RequestTemplate;public class FeignBasicAuthRequestInterceptor implements RequestInterceptor { @Override public void apply (RequestTemplate requestTemplate) { requestTemplate.header("Authorization" ,"Basic V3VaaGlZb25nOjEyMzQ1Ng==" ); } }
再配置上自定义的拦截器
1 2 3 4 5 6 7 8 @Configuration public class FeignConfiguration { @Bean public FeignBasicAuthRequestInterceptor basicAuthRequestInterceptor () { return new FeignBasicAuthRequestInterceptor (); } }
超时时间配置 链接超时 读取超时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Configuration public class FeignConfiguration { @Bean public Request.Options options () { return new Request .Options(50000L , TimeUnit.MILLISECONDS, 10000 ,TimeUnit.MILLISECONDS,true ); } }
客户端组件配置 Feign 中默认使用 JDK 原生的 URLConnection 发送 HTTP 请求,我们可以集成别的组件来替换它。比如Apache HttpClient.OkHttp.
配置OkHttp :
1 2 3 4 <dependency > <groupId > io.github.openfeign</groupId > <artifactId > feign-okhttp</artifactId > </dependency >
1 2 3 feign.httpclient.enabled =false feign.okhttp.enabled =true
1 2 3 4 org.springframework.cloud.openfeign.FeignAutoConfiguration.HttpClientFeignConfiguration org.springframework.cloud.openfeign.FeignAutoConfiguration.OkHttpFeignConfiguration
GZIP压缩配置 配置GZIP来压缩数据可以有效的节约网络资源,提升接口性能。
1 2 3 4 5 6 7 feign.compression.request.enabled =true feign.compression.response.enabled =true feign.compression.request.mime-types =text/xml,application/xml,application/json feign.compression.request.min-request-size =2048
注意:只有当Feign的httpClient 不是okhttp3的时候,压缩才会生效。(参看下面源代码)
1 org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration
配置编码器解码器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import feign.codec.Decoder;import feign.codec.Encoder;@Configuration public class FeignConfiguration { @Bean public Decoder decoder () { return new MyDecoder (); } @Bean public Encoder encoder () { return MyEncoder(); } }
使用自定义Feign的配置 在配置文件中自定义配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 feign.client.config.feignName.connectTimeout =5000 feign.client.config.feignName.readTimeout =5000 feign.client.config.feignName.loggerLevel =full feign.client.config.feignName.retryer =com.example.SimpleRetryer feign.client.config.feignName.requestInterceptors[0] =com.example.FooRequestInterceptor feign.client.config.feignName.requestInterceptors[1] =com.example.BarRequestInterceptor feign.client.config.feignName.encoder =com.example.SimpleEncoder feign.client.config.feignName.decoder =com.example.SimpleDecoder feign.client.config.feignName.contract =com.example.SimpleContract
其中 feignName 表示我们定义的 Feign 客户端里配置的服务名,即下面 @FeignClient 注解的 value 值。
1 2 3 4 5 6 7 @FeignClient(value = "eureka-client-service" ,configuration = FeignConfiguration.class) public interface UserRemoteClient { @GetMapping("/user/hello") String hello () ; }
继承特性 Feign 的继承特性可以让服务的接口定义单独抽出来,作为公共的依赖,以方便使用。
创建一个 maven quickstart 项目 命名为 feign-api
我们可看到 pom 中这个项目的坐标信息
1 2 3 <groupId > com.study</groupId > <artifactId > springcloud-feign-api</artifactId > <version > 1.0-SNAPSHOT</version >
加入 feign 和 spring cloud 相关依赖
再建立一个fegin 客户端类 (完了后运行编译,打包,install 看看有没有什么问题)
1 2 3 4 5 6 7 8 9 10 import org.springframework.cloud.openfeign.FeignClient;import org.springframework.web.bind.annotation.GetMapping;@FeignClient(value = "feign-provider") public interface UserRemoteClient { @GetMapping("/user/name") String getName () ; }
再建立一个spring cloud 项目 命名为 feign-provider。
引入 Eureka 的依赖 并把服务实例名配置为 feign-provider
引入 feign-api 项目的坐标作为依赖
建立一个类
1 2 3 4 5 6 7 8 9 import org.springframework.web.bind.annotation.RestController;@RestController public class UserController implements UserRemoteClient { @Override public String getName () { return "my name is wuzhiyong " ; } }
再建立一个spring cloud 项目 命名为 feign-consumer。(如果注册到 Eureka 就引入相关依赖并配置)
引入 feign-api 项目的坐标作为依赖
建立一个类
1 2 3 4 5 6 7 8 9 10 11 12 @RestController public class UserController { @Resource UserRemoteClient userRemoteClient; @GetMapping("/call") public String call () { return userRemoteClient.getName(); } }
在启动类上配置 feign client 扫包
1 2 3 4 5 6 7 8 @SpringBootApplication @EnableFeignClients(basePackages = "com.study") public class SpringcloudFeignConsumerApplication { public static void main (String[] args) { SpringApplication.run(SpringcloudFeignConsumerApplication.class, args); } }
分别启动 注册中心,feign-provider ,feign-consumer 访问 feign-consumer 的 call 接口可以得到 feign-provider 返回的数据。
多参数请求构造 多参数请求构造分为 GET 请求和 POST 请求两种方式
feign-api 添加如下。
1 2 3 4 5 6 7 8 @GetMapping("/user/info") User getUserInfo (@RequestParam("name") String name, @RequestParam("age") int age) ; @GetMapping("/user/detail") String getUserDetail (@RequestParam Map<String, Object> param) ; @PostMapping("/user/add") String addUser (@RequestBody User user) ;
书中说 addUser 实现类 也要加上 @RequestBody 注解,我试了下没加上也可以。然后传map 参数我这边后台接收不到,一直解析不成功。(可能是没有加第三方解析json的依赖的缘故吧)
脱离 spring cloud 使用 Feign 原生注解方式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import feign.Body;import feign.Headers;import feign.Param;import feign.RequestLine;public interface UserRemoteClient { @RequestLine(value = "GET user/hello") String hello () ; @RequestLine("GET /repos/{id}/{name}/xxx") public String getRepos (@Param("id") int id,@Param("name") String name) ; @RequestLine("POST /user/{name}") public String addUser (@Param("name") String name) ; @RequestLine("POST /user") @Headers("Content-Type:application/json") @Body("%7B\"username\":\"{user_name}\",\"password\":{pwd}\"%7D") String postUser (@Param("user_name") String name,@Param("pwd") String pwd) ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @RestController public class UserController { private UserRemoteClient userRemoteClient = Feign.builder().target(UserRemoteClient.class,"http://localhost:8081" ); @GetMapping("/call/hello") public String cellHello () { System.out.println("8888888888888888888888888888" ); return userRemoteClient.hello(); } @GetMapping("/repos/{id}/{name}/xxx") public String getRepos (@PathVariable("id") int id, @PathVariable("name") String name) { return userRemoteClient.getRepos(id,name); } @PostMapping("/user/{name}") public String addUser (@PathVariable("name") String name) { return userRemoteClient.addUser(name); } @PostMapping("/user") String postUser (@RequestParam("user_name") String name, @RequestParam("pwd") String pwd) { return userRemoteClient.postUser(name,pwd); } }
依赖可以用 springcloud feign 的依赖。也可以用 feign 本身的依赖
1 2 3 4 5 <dependency > <groupId > io.github.openfeign</groupId > <artifactId > feign-core</artifactId > <version > 10.10.1</version > </dependency >
构建 Feign 对象 feign 是通过 builder 模式来构建代理对象的。我们可以写一个通用的工具类来构建对象。
1 2 3 4 5 6 7 import feign.Feign;public class RestApiCallUtils { public static <T> T getRestClient (Class<T> apiType,String url) { return Feign.builder().target(apiType,url); } }
控制器中这样调用:
1 2 3 4 5 6 7 8 9 10 @RestController public class UserController { private UserRemoteClient userRemoteClient = RestApiCallUtils.getRestClient(UserRemoteClient.class,"http://localhost:8081" ); @GetMapping("/call/hello") public String cellHello () { System.out.println("8888888888888888888888888888" ); return userRemoteClient.hello(); } }
注意:在调用Feign.builder().target()方法的时候传的 url 是包括 http:// 前缀的。否则控制台会报错:values must be absolute.
其它配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class RestApiCallUtils { public static <T> T getRestClient (Class<T> apiType,String url) { return Feign.builder() .encoder( new MyEncoder ()) .decoder( new MyDecoder ()) .logger( new Logger .JavaLogger().appendToFile(System.getProperties().getProperty("logpath" )+"/http.log" )).logLevel(Logger.Level.BASIC) .options( new Request .Options(10000 ,10000 )) .requestInterceptor( new MyInterceptor ()) .client( new OkHttpClient ()) .retryer(new Retryer .Default()) .target(apiType,url); } }
参考:
《spring cloud 微服务 入门、进阶与实战》
书中代码:
https://github.com/yinjihuan/spring-cloud/tree/master/Spring-Cloud-Book-Code-2/ch-5