Feign远程调用
一.feign远程调用
1.导入包
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.加上注解@EnableFeignClients
3.编写client
//name 服务名
@FeignClient(name = "spring-cloud-alibaba-nacos")
public interface UserCenterFeignClient {
@GetMapping("/user/{id}")
String user(@PathVariable String id) ;
}
二.feign细粒度配置自定义-代码方式
级别 | 打印内容 |
---|---|
NONE(默认值) | 不记录任何日志 |
BASIC(适合生产环境使用) | 仅记录请求方法,URL,响应代码以及执行时间 |
HEADERS | 记录BASIC级别的基础上,记录请求和响应的header |
FULL | 记录请求和响应的header,body元数据 |
1.编写配置类
/**
* feign的配置类
* 这个类别加@configuration,否则必须挪到@ComponentScan扫描不到的包,及application意外险
* 否则父子上下文重叠
*/
public class UserCenterFeignConfiguration {
@Bean
public Logger.Level level(){
//让feign打印所有请求
return Logger.Level.FULL;
}
}
2.@FeignClient注解增加配置
@FeignClient(name = "spring-cloud-alibaba-nacos",configuration = UserCenterFeignConfiguration.class)
public interface UserCenterFeignClient {
@GetMapping("/user/{id}")
String user(@PathVariable String id) ;
}
3.修改日志级别
logging:
level:
com.yz.alibaba.feignclient.UserCenterFeignClient: debug
二.feign细粒度配置自定义-属性配置
修改配置
logging:
level:
com.yz.alibaba.feignclient.UserCenterFeignClient: debug
feign:
client:
config:
spring-cloud-alibaba-nacos:
loggerLevel: full
三.feign细粒度配置自定义-全局配置
-
修改启动类注解@EnableFeignClients(defaultConfiguration = UserCenterFeignConfiguration.class) UserCenterFeignConfiguration为自定义配置类
-
修改配置文件
logging:
level:
com.yz.alibaba.feignclient.UserCenterFeignClient: debug
feign:
client:
config:
default:
loggerLevel: full
四.支持的配置项
private Level loggerLevel; //日志级别
private Integer connectTimeout; //链接超时时间
private Integer readTimeout; //读取超时时间
private Class<Retryer> retryer; //重试策略
private Class<ErrorDecoder> errorDecoder; //错误解码器
private List<Class<RequestInterceptor>> requestInterceptors; //拦截器
private Boolean decode404; //是否对404错误码解码
private Class<Decoder> decoder; //解码器
private Class<Encoder> encoder; //编码器
private Class<Contract> contract; //契约
private ExceptionPropagationPolicy exceptionPropagationPolicy; //异常传播策略
五.代码配置,属性配置对比
配置方式 | 优点 | 缺点 |
---|---|---|
代码方式 | 基于代码,更加灵活 | 如果Feign的配置类加了@Configuration注解,需要注意父子上下文, 线上修改得重新打包 |
属性配置 | 易上手 配置更加直观 线上修改无需重新打包发布,优先级更高 |
极端场景下,没有代码配置方式更加灵活 |
六.@FeignClient注解异常
Description:The bean 'spring-cloud-alibaba-nacos.FeignClientSpecification' could not be registered. A bean with that name has already been defined and overriding is disabled.Action:Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
@FeignClient name重复,在配置文件增加配置spring.main.allow-bean-definition-overriding=true
七.feign继承
spring官方不推荐使用,不符合微服务特性,但是挺多公司使用,一致性,方便
使用方式
公共访问接口-> 服务提供者实现公共访问接口-> feign继承公共访问接口->接口调用feign
1. 公共访问接口-公共服务类
@RequestMapping("/hs2")public interface InheritanceService { @RequestMapping(value = "/hello1", method = RequestMethod.GET) String hello(@RequestParam("name") String name); @RequestMapping(value = "/hello2", method = RequestMethod.GET) Book hello(@RequestHeader("name") String name, @RequestHeader("author") String author, @RequestHeader("price") Integer price) throws UnsupportedEncodingException; @RequestMapping(value = "/hello3", method = RequestMethod.POST) String hello(@RequestBody Book book);}
2.服务提供者实现公共访问接口-服务A
@RestControllerpublic class InheritanceFeignController implements InheritanceService { @Override public String hello(@RequestParam("name") String name) { return "hello " + name + "!"; } @Override public Book hello(@RequestHeader("name") String name, @RequestHeader("author") String author, @RequestHeader("price") Integer price) throws UnsupportedEncodingException { Book book = new Book(); book.setName(URLDecoder.decode(name,"UTF-8")); book.setAuthor(URLDecoder.decode(author,"UTF-8")); book.setPrice(price); System.out.println(book); return book; } @Override public String hello(@RequestBody Book book) { return "书名为:" + book.getName() + ";作者为:" + book.getAuthor(); }}
3.feign继承公共访问接口-服务B
@FeignClient("spring-cloud-alibaba-nacos")public interface InheritanceFeignClient extends InheritanceService {}
4.接口调用feign-服务B
@RestController@RequiredArgsConstructor(onConstructor = @_(@Autowired))public class InheritanceFeignController { private final InheritanceService inheritanceService; @RequestMapping("/hello1") public String hello1() { return inheritanceService.hello("张三"); } @RequestMapping(value = "/hello2") public Book hello2() throws UnsupportedEncodingException { Book book = inheritanceService.hello(URLEncoder.encode("三国演义","UTF-8"), URLEncoder.encode("罗贯中","UTF-8"), 33); System.out.println(book); return book; } @RequestMapping("/hello3") public String hello3() { Book book = new Book(); book.setName("红楼梦"); book.setPrice(44); book.setAuthor("曹雪芹"); return inheritanceService.hello(book); }}
八.如何使用Feign构造多参数的请求
GET请求多参数的URL
假设需请求的URL包含多个参数,例如http://microservice-provider-user/get?id=1&username=张三
,该如何使用Feign构造呢?
我们知道,Spring Cloud为Feign添加了Spring MVC的注解支持,那么我们不妨按照Spring MVC的写法尝试一下:
@FeignClient("microservice-provider-user")public interface UserFeignClient { @RequestMapping(value = "/get", method = RequestMethod.GET) public User get0(User user);}
然而,这种写法并不正确,控制台会输出类似如下的异常。
feign.FeignException: status 405 reading UserFeignClient#get0(User); content:{"timestamp":1482676142940,"status":405,"error":"Method Not Allowed","exception":"org.springframework.web.HttpRequestMethodNotSupportedException","message":"Request method 'POST' not supported","path":"/get"}
由异常可知,尽管我们指定了GET方法,Feign依然会使用POST方法发送请求。于是导致了异常。正确写法如下
方法一[推荐]
@FeignClient("microservice-provider-user")public interface UserFeignClient { @GetMapping("/get") public User get0(@SpringQueryMap User user);}
方法二[推荐]
@FeignClient(name = "microservice-provider-user")public interface UserFeignClient { @RequestMapping(value = "/get", method = RequestMethod.GET) public User get1(@RequestParam("id") Long id, @RequestParam("username") String username);}
这是最为直观的方式,URL有几个参数,Feign接口中的方法就有几个参数。使用@RequestParam注解指定请求的参数是什么。
方法三[不推荐]
多参数的URL也可使用Map来构建。当目标URL参数非常多的时候,可使用这种方式简化Feign接口的编写。
@FeignClient(name = "microservice-provider-user")public interface UserFeignClient { @RequestMapping(value = "/get", method = RequestMethod.GET) public User get2(@RequestParam Map<String, Object> map);}
在调用时,可使用类似以下的代码。
public User get(String username, String password) { HashMap<String, Object> map = Maps.newHashMap(); map.put("id", "1"); map.put("username", "张三"); return this.userFeignClient.get2(map);}
注意:这种方式不建议使用。主要是因为可读性不好,而且如果参数为空的时候会有一些问题,例如map.put("username", null);
会导致microservice-provider-user
服务接收到的username是""
,而不是null。
POST请求包含多个参数
下面来讨论如何使用Feign构造包含多个参数的POST请求。假设服务提供者的Controller是这样编写的:
@RestControllerpublic class UserController { @PostMapping("/post") public User post(@RequestBody User user) { ... }}
我们要如何使用Feign去请求呢?答案非常简单,示例:
@FeignClient(name = "microservice-provider-user")public interface UserFeignClient { @RequestMapping(value = "/post", method = RequestMethod.POST) public User post(@RequestBody User user);}
九.RestTemplate VS Feign
角度 | RestTemplate | Feign |
---|---|---|
可读性,可维护性 | 一般 | 极佳 |
开发体验 | 欠佳 | 极佳 |
性能 | 很好 | 中等 |
灵活性 | 极佳 | 中等 |
十.feign线程池
导入包
<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId></dependency>
还可以使用
<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-okhttp</artifactId></dependency>
feign: #okhttp httpclient: #是否开启 enabled: true #最大连接数 max-connections: 200 #最大路由 max-connections-per-route: 50 #存活时间 time-to-live: 900 #存活时间单位 time-to-live-unit: minutes #跟随重定向 follow-redirects: true #连接超时时间 connection-timeout: 2000 #超时重试时间 connection-timer-repeat: 3000