SpringCloud集成OpenFeign服务接口调用

OpenFeign简介

什么是Feign?

Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单。它的使用方法是定义一个服务接口然后在上面添加注解。Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用支持负载均衡。

Feign是能干什么?

Feign旨在使编写Java Http客户端变得更容易。前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模板化的调用方法。但是在实际开发过程中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由它来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需要创建一个接口并使用注解的方式来配置它,(以前是Dao接口上面标准Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可)即可完成对服务提供方的接口绑定,简化了Spring  Cloud Ribbon时,自动封装服务调用客户端的开发量。

Feign集成了Ribbon,利用Ribbon维护了服务列表信息,并且通过轮询实现了客户端的负载均衡,而与Ribbon不同的是,通过Feign值需要定义服务绑定接口且一声明式的方法,优雅而简单的实现了服务调用。

Feign和OpenFeign的区别

SpringCloud使用OpenFeign

基础用法

1、引入相关的依赖

<!-- SpringCloud Feign在Hoxton.M2 RELEASED版本之后不再使用Ribbon而是使用spring-cloud-loadbalancer -->
<!-- ribbon相关的依赖不能与loadbalancer 同时存在,否则奇奇怪怪的问题一大堆, 可能连应用都起不来 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </exclusion>
        <!--openfeign-core包含了ribbon,先排除掉再进行单独的引入-->
        <exclusion>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-openfeign-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-openfeign-core</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-netflix-ribbon</artifactId>
        </exclusion>
    </exclusions>
</dependency>

注:ribbon相关的依赖不能与loadbalancer 同时存在,否则奇奇怪怪的问题一大堆, 可能连应用都起不来。

如果使用eureka作为注册中心,eureka client(spring-cloud-starter-netflix-eureka-client)已经包含了spring-cloud-starter-loadbalancer,我们不用再单独引入(即上面的spring-cloud-starter-loadbalancer依赖引入可以去掉)。

2、启动类上添加@EnableFeignClients注解,用于开启@FeignClient注解扫描

@EnableFeignClients //开启@FeignClient注解扫描
@EnableDiscoveryClient //客户端开启服务注册和发现
@SpringBootApplication
public class UserApp {
    public static void main(String[] args) {
        SpringApplication.run(UserApp.class, args);
    }
}

3、添加一个Feign 接口,@FeignClient 指定要调用的服务名,也就是在服务在注册中心中注册的服务名,

@FeignClient(name = "cloud-order-service") //指定要调用的服务名
public interface OrderFeign {

    @RequestMapping("/order/findOrdersByUserId")
    Map<String, Object> findOrdersByUserId(@RequestParam("userId") Long userId);
}

4、controller接口调用Feign接口

@RestController
@RequestMapping("/user")
public class UserController {

    private Logger logger = LoggerFactory.getLogger(UserController.class);

    @Autowired
    private OrderFeign orderFeign;

    @RequestMapping("/findOrdersByUserId")
    public Map<String, Object> findOrdersByUserId(@RequestParam("userId") Long userId) {
        return orderFeign.findOrdersByUserId(userId);
    }
}

application.yml常用配置

# open feign 配置
feign:
  compression:
    #开启压缩可以有效节约网络资源,但是会增加CPU压力,建议把最小压缩的文档大小适度调大一点
    request:
      #配置请求GZIP压缩
      enabled: true
      #配置压缩支持的MIME TYPE
      mime-types: text/xml,application/xml,application/json
      #单位:字节
      min-request-size: 2048
    response:
      enabled: true
      #启用默认的gzip解码器
      useGzipDecoder: true
  httpclient:
    #是否开启Apache HTTP请求方式
    enabled: true
    #连接超时时间(单位:毫秒)
    connection-timeout: 2000
    #线程池最大连接数(全局)
    max-connections: 200
    #线程池最大连接数(单个HOST)
    max-connections-per-route: 50
    #线程存活时间(单位:毫秒)
    time-to-live: 900
  okhttp:
    #是否开启OK HTTP请求方式
    enabled: false
  hystrix:
    #是否开启hystrix功能
    enabled: false
  client:
    #对应属性配置类:org.springframework.cloud.openfeign.feignClientProperties.FeignClientConfiguration
    config:
      # feign请求默认配置
      default:
        #连接超时时间
        connectTimeout: 2000
        #读超时时间
        readTimeout: 3000
        #日志级别
        loggerLevel: full
        #异常处理
        errorDecoder: feign.codec.ErrorDecoder.Default
        #重试试策略
        retryer: feign.Retryer.Default
        #默认参数条件
        defaultQueryParameters:
          #query: queryValue
          userName: linhongwei
        #默认header
        defaultRequestHeaders:
          #header: headerValue
        #默认拦截器
        requestInterceptors:
          - org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingInterceptor
          - org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingInterceptor
        #404响应 true-直接返回,false-抛出异常
        decode404: false
        #传输编码
        encoder: org.springframework.cloud.openfeign.support.SpringEncoder
        #传输解码
        decoder: feign.optionals.OptionalDecoder
        #传输协议
        contract: org.springframework.cloud.openfeign.support.SpringMvcContract
      #对应@FeignClient注解的name属性, 优先default配置
      cloud-order-service:
        loggerLevel: debug

Openfeign实现日志打印

Openfeign提供了日志打印功能。比如我们消费者服务调用生产者的服务的时候在接口调用的时候,我们可能需要更详细的信息,如信息头、状态码、时间、接口等等。就可以使用Openfeign日志打印功能,我们可以通过配置来调整日志级别,从而了解Feign中Http请求的细节。也就是对Feign接口的调用情况进行监控和输出。

Logger有四种类型:

  • NONE:默认的,不显示任何日志。
  • BASIC:仅记录请求方法、URL、响应状态及执行时间。
  • HEADERS:除了BASIC中定义的信息之外,还有请求和响应的有信息。
  • FULL:除了BASIC中定义的信息之外,还有请求和响应的正文及元数据。

通过注册Bean来设置日志记录级别!

@Configuration
public class FeignConfig {
    /**
     * feignClient配置日志级别
     *
     * @return
     */
    @Bean
    public Logger.Level feignLoggerLevel() {
        // 请求和响应的头信息,请求和响应的正文及元数据
        return Logger.Level.FULL;
    }
}

可以在yml中指定日志级别:

logging:
  level:
    # feign日志以什么级别监控哪个接口
    com.harvey.demo.feign.OrderFeign: debug

OpenFegin传参总结

GET请求

1、单参数

必须加注解**@RequestParam** 否则请求不通过,消费者默认变为post请求,可能status 405 :

//消费报错
feign.FeignException$MethodNotAllowed: status 405 reading FirstFeignService#firstParam(String)
//提供者警告
Resolved [org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported]

2、多参数

必须加注解 @RequestParam 否则启动报错。

//错误
String secondParam(String uname, Integer age);
//报错信息
//Caused by: java.lang.IllegalStateException: Method has too many Body parameters
//正确写法
String secondParam(@RequestParam String uname,  @RequestParam  Integer age);

3、对象传参

使用@SpringQueryMap

OpenFeign@QueryMap批注支持将POJO用作GET参数映射。不幸的是,默认的OpenFeign QueryMap注释与Spring不兼容,因为它缺少value属性。

Spring Cloud OpenFeign提供了等效的@SpringQueryMap注释用于注释POJO或Map参数作为查询参数映射。

示例:

1)消费者调用

//消费者调用
/**无参*/
@GetMapping(value = "frist/uuid")
String  getUUid();
/**get 单参*/
@GetMapping(value = "frist/firstParamo")
String firstParam(@RequestParam  String uname);
/**get 多参*/
@GetMapping(value = "frist/secondParam")
String secondParam(@RequestParam  String uname,  @RequestParam  Integer age);
/**get 对象*/
@GetMapping(value = "frist/test/pojo")
String testGetPojo(@SpringQueryMap  TestVo testVo);

2)提供者

@RequestMapping("/uuid")
public String fristTest(){
    return UUID.randomUUID().toString();
}

/**get 单参*/
@GetMapping(value = "/firstParamo")
String firstParam(String uname){
    System.out.println("firstParamo:"+uname);
    return "firstParamo:"+uname;
}
/**get 多参*/
@GetMapping(value = "/secondParam")
String secondParam(String uname, Integer age){
    System.out.println("secondParam:"+uname+":"+age);
    return "secondParam:"+uname+":"+age;
}

/**get 对象*/
@GetMapping("/test/pojo")
public  String testGetPojo(TestVo testVo){
    System.out.println(testVo);
    if(null!=testVo ||StringUtils.isNotBlank(testVo.getUname())||null!=testVo.getAge()){
        return  "数据不为空";
    }else {
        return "数据为空";
    }

}

POST请求

使用@RequestBody 默认将请求转为post。

1、单参

注意:不加注解启动调用都不会报错,但是传参结果为null

  • 使用 @RequestParam 提供者无需加
  • 使用 @RequestBody 提供者必须加 @RequestBody 否则则无法获取,结果为null

2、多参数

1)所有参数都加 @RequestParam

2)混合使用 @RequestBody @RequestParam

  • 最多只能有一个参数是@RequestBody指明的,其余的参数必须使用@RequestParam指明
  • 使用@RequestBody 必须用@RequestBody 接收

3、对象

  • 使用 @SpringQueryMap
  • 使用 @RequestBody

示例:

1)消费者

@PostMapping(value = "frist/postFirstParam")
String postFirstParam(@RequestBody  String uname);
// String postFirstParam(  @RequestParam  String uname);
@PostMapping(value = "frist/postSecondParam")
String postSecondParam(@RequestParam String uname, @RequestBody Integer age);
@PostMapping(value = "frist/postTestGetPojo")
String postTestGetPojo(@RequestBody TestVo testVo);
//String postTestGetPojo(@SpringQueryMapTestVo testVo);

2)提供者

@PostMapping(value = "/postFirstParam")
//RequestParam  
// String postFirstParam( String uname){
String postFirstParam(@RequestBody String uname){
    System.out.println("postFirstParam:"+uname);
    return "postFirstParam:"+uname;
}

@PostMapping(value = "/postSecondParam")
String postSecondParam(String uname, @RequestBody Integer age){
    System.out.println("postSecondParam:"+uname+":"+age);
    return "postSecondParam:"+uname+":"+age;
}

@PostMapping(value = "/postTestGetPojo")
//SpringQueryMap
//  String postTestGetPojo(@RequestBody TestVo testVo){
String postTestGetPojo(@RequestBody TestVo testVo){
    System.out.println("postTestGetPojo:"+testVo);
    return "postTestGetPojo";
}

上面我们只是简单的GET和POST请求进行区分,还有一种是写在请求路径上的:

//消费者
@DeleteMapping("/user2/{id}")
void deleteUser2(@PathVariable("id") Integer id);

//提供者
@DeleteMapping("/user2/{id}")
public void deleteUser2(@PathVariable Integer id){
    System.out.println(id);
}

 

posted @ 2022-05-04 10:56  残城碎梦  阅读(528)  评论(0编辑  收藏  举报