文章目录
使用Feign实现声明式REST调用
在之前的电影微服务中,是通过http://127.0.0.1:8880/movie/users
来访问所有的用户信息的。如果要根据某个条件查询,如user_id=1。那么代码就得如下所示:
public ServiceUser findUserById(Long id){
return this.ribbonRestTemplate,getForObject("http://127.0.0.1:8880/movie/"+userId,ServiceUser.class)
}
这样就会不可避免的在代码中通过拼接的形式构造URL。如果很长,可能还会出现下面这样的形势:
http://127.0.0.1:8880/movie?user_name=张三&user_age=20&user_sex=男
如果URL比较短还好,一旦变得很长,比如有十个以上的参数,那么代码就变得难以阅读和维护。Feign正是为了解决这种问题
Feign简介
- 官网:链接
- Github:Feign的Github
Feign时Netfli开发的声明式、模板化的HTTP客户端,其灵感来自Retrofit、JAXRS-2.0以及WebSocket。Feign可以帮助我们更加便捷、优雅地调用HTTP API.
在Spring Cloud中,使用Feign非常简单——创建一个接口,并在接口上添加一些注解,代码就完成了。Feign支持多种注解,例如Feign自带的注解或者JAX-RS注解等。
Spring Cloud对Feign进行了增强,使Feign支持了Spring MVC注解,并整合了Ribbon和Eureka,从而让Feign使用起来更加简单。
微服务消费者整合Feign
- 为电影微服务增加Feign依赖
compile group: 'org.springframework.cloud', name: 'spring-cloud-starter-openfeign', version: '2.1.2.RELEASE'
这里注意下依赖版本,否则可能报如下错误
Caused by: java.io.FileNotFoundException: class path resource [org/springframework/boot/autoconfigure/web/ServerPropertiesAutoConfiguration.class] cannot be opened because it does not exist
at org.springframework.core.io.ClassPathResource.getInputStream(ClassPathResource.java:180)
at org.springframework.core.type.classreading.SimpleMetadataReader.<init>(SimpleMetadataReader.java:51)
at org.springframework.core.type.classreading.SimpleMetadataReaderFactory.getMetadataReader(SimpleMetadataReaderFactory.java:103)
at org.springframework.boot.type.classreading.ConcurrentReferenceCachingMetadataReaderFactory.createMetadataReader(ConcurrentReferenceCachingMetadataReaderFactory.java:88)
- 创建一个Feign接口,并添加
@FeignClient
注解
@FeignClient(name = "micro-user-service")
public interface UserFeignClient {
@RequestMapping(value = "/users/{id}",method = RequestMethod.GET)
/**
* 根据ID获取用户信息
*/
ServiceUser findById(@PathVariable(value = "id") Long id);
}
这里的写法也要注意,如果写成
ServiceUser findById(@PathVariable Long id);
可能会报下面错误:
Feign PathVariable annotation was empty on param 0.
- 改造用户微服务,增加一个根据ID查询用户的方法
@ResponseBody
@RequestMapping(value = "/users/{userId}", method = {RequestMethod.GET})
public ServiceUser selectUserById(@PathVariable Long userId){
ServiceUser serviceUser = userRepository.findById(userId).get();
return serviceUser;
}
- 改造电影微服务,增加一个使用FeignClient获取用户信息的方法
@Controller
public class MovieController {
private static final Logger logger = LoggerFactory.getLogger(MovieController.class);
@Bean
@LoadBalanced
RestTemplate loadBalancedRestTemplate() {
return new RestTemplate();
}
@Autowired
private RestTemplate restTemplate;
@Autowired
private LoadBalancerClient loadBalancerClient;
@Autowired
private UserFeignClient userFeignClient;
@ResponseBody
@RequestMapping(value = "/movie/users", method = {RequestMethod.GET})
public List<ServiceUser> listUsers(){
return this.restTemplate.getForObject("http://micro-user-service/users",List.class);
}
@ResponseBody
@RequestMapping(value = "/log-instalce", method = {RequestMethod.GET})
public void logUserInstance(){
ServiceInstance serviceInstance = this.loadBalancerClient.choose("micro-user-service");
logger.info("{}:{}:{}",serviceInstance.getServiceId(),serviceInstance.getHost(),serviceInstance.getPort());
}
@ResponseBody
@RequestMapping(value = "/movie/{id}", method = {RequestMethod.GET})
public ServiceUser getUserByUserId(@PathVariable Long id){
return userFeignClient.findById(id);
}
}
- 启动Eureka Server集群
- 启动用户微服务集群
- 启动电影微服务
- 使用postman发送请求,查询结果如下
- 用户微服务中打印如下日志:
自定义Feign服务
在Spring Cloud中,Feign的默认配置类是FeignClientsConfiguration,该类定义了Feign默认使用的编码器、解码器、所使用的契约等。
Spring Cloud中能够通过注解@FeignClient的Configuration属性自定义Feign配置,且自定义配置的优先级高于默认配置。
- 创建Feign配置类
/**
* 该配置类为Feign的配置类
* 注意:该类不应该在主类程序上下文的@CompanentScan中,否则该类的信息会被所有的FeignClient共享
*/
@Configuration
public class FeignConfiguration {
/**
* 将契约修改为Feign的默认契约。这样可以使用Feign自带的注解
* @return 默认的Feign契约
*/
@Bean
public Contract feignContract(){
return new feign.Contract.Default();
}
}
- 新增一个FeignClient并指定使用上面的配置类
/**
* 使用@FignClient的configuration属性,指定feign的配置类
*/
@FeignClient(name = "micro-user-service",configuration = FeignConfiguration.class)
public interface UserFeignClientCustom {
/**
* 根据ID获取用户信息
*/
@RequestLine("GET /users/{id}")
ServiceUser findById(@PathVariable(value = "id") Long id);
}
- 这样就会存在两个都指定
micro-user-service
的FeignClient,启动报错如下:
Description:
The bean 'micro-user-service.FeignClientSpecification', defined in null, could not be registered. A bean with that name has already been defined in null and overriding is disabled.
Action:
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
这是因为在SpringBoot 2.xx之后,出现相同的@FeignClient(name = 相同的名字
就会报错。这里我们在原来的基础上修改,改成如下:
/**
* 使用@FignClient的configuration属性,指定feign的配置类
*/
@FeignClient(name = "micro-user-service",configuration = FeignConfiguration.class)
public interface UserFeignClient{
/**
* 根据ID获取用户信息
*/
@RequestLine("GET /users/{id}")
ServiceUser findById(@Param("id") Long id);
}
再启动,即可正常。
- 接下来使用Postman做下测试,结果如下:
测试正常
Feign对继承的支持
Feign支持继承。使用继承,可将一些公共操作分组到一些父接口中,从而简化Feign的开发。如:
- 基础接口:UserService.java
public interface UserService{
@RequestMapping(method = RequestMethod.GET, value="/users/id")
ServiceUser getUser(@PathVariable("id) Long id)
}
- 服务提供者Controller:UserResource.java
@RestController
public class UserResource implements UserService(){
//...
}
- 服务消费者:UserClient.java
@FeignClient("users")
public interface UserClient extentds UserService(){
}
这样虽然可以简化开发,但是消费者与提供者共用父类接口。这无形中增加了系统的耦合。
Feign对压缩的支持
一些场景下,可能需要对请求或响应进行压缩,此时可使用以下属性启用Feign的压缩功能,配置如下::
feign:
compression:
# 启用请求和响应的压缩
request:
enabled: true
#用于支持的媒体类型列表,默认是text/xml,application/xml,application/json
mime-types: text/xml,application/xml,application/json
#请求的最小阈值,默认2048
min-request-size: 2048
response:
enabled: true
Feign的日志
很多场景下,需要了解Feign的处理细节,这时可以使用Feign的日志功能。
Feign对日志的处理非常灵活,可为每个Feign指定日志策略,每个Feign客户端都会创建一个Logger。默认情况下,logger的名称是Feign接口的完整类名。需要注意的是,Feign的日志打印只会对DEBUG级别做出响应。
可以为每个Feign客户端配置各自的Logger.Level对象,告诉Feign记录那些日志,级别如下
Feign的日志级别
- NONE:不记录日志(默认)
- BASIC:仅记录请求方法、URL、响应状态码以及执行时间
- HEADERS:记录BASIC级别的基础上,记录请求和响应的HEADER
- FULL:记录请求和响应的header、body和元数据
配置过程
- 编写配置类,可在之前的类上进行修改,增加以下配置
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
- 修改配置文件,指定FeignClient的日志级别为DEBUG
logging:
level:
# 因为Feign只会对DEBUG级别的日志做出响应
org.virtue.feign.UserFeignClient: DEBUG
- 依次启动Eureka Server集群、用户微服务集群、电影微服务,使用Postman访问,日志如下:
2019-07-16 23:26:16.479 DEBUG 40144 --- [nio-8880-exec-1] org.virtue.feign.UserFeignClient : [UserFeignClient#findById] <--- HTTP/1.1 200 (439ms)
2019-07-16 23:26:16.480 DEBUG 40144 --- [nio-8880-exec-1] org.virtue.feign.UserFeignClient : [UserFeignClient#findById] content-type: application/json;charset=UTF-8
2019-07-16 23:26:16.480 DEBUG 40144 --- [nio-8880-exec-1] org.virtue.feign.UserFeignClient : [UserFeignClient#findById] date: Tue, 16 Jul 2019 15:26:16 GMT
2019-07-16 23:26:16.480 DEBUG 40144 --- [nio-8880-exec-1] org.virtue.feign.UserFeignClient : [UserFeignClient#findById] transfer-encoding: chunked
2019-07-16 23:26:16.480 DEBUG 40144 --- [nio-8880-exec-1] org.virtue.feign.UserFeignClient : [UserFeignClient#findById]
2019-07-16 23:26:16.484 DEBUG 40144 --- [nio-8880-exec-1] org.virtue.feign.UserFeignClient : [UserFeignClient#findById] {"userId":1,"username":"张三","password":"111","age":20}
2019-07-16 23:26:16.484 DEBUG 40144 --- [nio-8880-exec-1] org.virtue.feign.UserFeignClient : [UserFeignClient#findById] <--- END HTTP (58-byte body)
使用Feign构造多参数请求
GET多参数请求
- 写法一
@RequestMapping(value = "/get",method = RequestMethod.GET)
ServiceUser findUserByIdAndUsername(@RequestParam("id") Long id,@RequestParam("username") String suername);
- 写法二
@RequestMapping(value = "/get",method = RequestMethod.GET)
ServiceUser findUserByIdAndUsername(Map<String,Object> reqMap);
调用时只用将请求key和value放在map中就行,如:
public ServiceUser getUser(String username,Long id){
HashMap<String,Object> map = Maps.newHashMap();
map.put("id",1);
map.put("username","张三");
return userFeignClient.findUserByIdAndUsername(map);
}
POST的请求参数
如,服务提供者的Controller
@RequestController
public class UserController{
@PostMapping("/postTest")
public User post(@RequestBody User user){
//***
}
}
对应的Feign可如下:
@FeignClient(name="xxx")
public interface UserFeignClient(){
public User post(@RequestBody User user);
}
代码地址
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/13156.html