使用Feign实现声明式REST调用

13 篇文章 0 订阅
9 篇文章 0 订阅

使用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简介

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);
}

代码地址

github地址链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值