欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Spring Cloud Feign实例讲解学习

程序员文章站 2022-06-30 23:46:47
前面博文搭建了一个eureka+ribbon+hystrix的框架,虽然可以基本满足服务之间的调用,但是代码看起来实在丑陋,每次客户端都要写一个resttemplate,为...

前面博文搭建了一个eureka+ribbon+hystrix的框架,虽然可以基本满足服务之间的调用,但是代码看起来实在丑陋,每次客户端都要写一个resttemplate,为了让调用更美观,可读性更强,现在我们开始学习使用feign。

feign包含了ribbon和hystrix,这个在实战中才慢慢体会到它的意义,所谓的包含并不是feign的jar包包含有ribbon和hystrix的jar包这种物理上的包含,而是feign的功能包含了其他两者的功能这种逻辑上的包含。简言之:feign能干ribbon和hystrix的事情,但是要用ribbon和hystrix自带的注解必须要引入相应的jar包才可以。

案例一:

eureka注册中心:

服务提供方:

服务调用方:

服务提供方就是个简单的eurekaclient端+web应用,提供以下方法

@restcontroller 
@requestmapping("/feign-service") 
public class helloservicecontorller { 
  private logger logger = loggerfactory.getlogger(this.getclass());    
  private void sleep(string methodname) { 
    int sleepmintime = new random().nextint(3000); 
    logger.info("helloservice "+methodname+" sleepmintime: "+sleepmintime); 
    try { 
      thread.sleep(sleepmintime); 
    } catch (interruptedexception e) { 
      e.printstacktrace(); 
    } 
  } 
   
  @requestmapping(value="/serviceget",method=requestmethod.get) 
  public string helloservice(@requestparam string name) { 
    sleep("get"); 
    return "helloserviceimpl name :"+name; 
  } 
   
  @requestmapping(value="/servicehead", method=requestmethod.head) 
  public string helloservice(@requestheader string name, 
      @requestheader string password) { 
    sleep("header"); 
    return "helloservicehead name :"+name +" password:"+password; 
  } 
   
  @requestmapping(value="/servicepost", method=requestmethod.post) 
  public string helloservice(@requestbody userdemo userdemo) { 
    sleep("post"); 
    return userdemo.tostring(); 
  } 
} 

需要注意的以下注解不可以省略。

@requestparam:annotation which indicates that amethod parameter should be bound to a web request parameter

@requestbody:annotation indicating a methodparameter should be bound to the body of the web request.

@requestheader:annotation which indicates that amethod parameter should be bound to a web request header.

如果缺少了以上注解,服务运行起来以后虽然不会报错,但是获取不到入参。

服务调用方项目:

<dependency> 
      <groupid>org.springframework.cloud</groupid> 
      <artifactid>spring-cloud-starter-feign</artifactid> 
    </dependency> 

这里只依赖了feign,没有依赖ribbon和hystrix。

application.yml:

server: 
 port: 9051 
 
spring: 
 application: 
  name: demo-feign-freeconsumer 
   
eureka: 
 client: 
  serviceurl: 
   defaultzone: http://peer1:1111/eureka/,http://peer2:1112/eureka/ 
feign: 
 hystrix: 
  enabled: true 
 
#ribbon 超时时间设置 
#ribbon: 
# connecttimeout: 500 
# readtimeout: 3000

hystrix这个配置坑了我好久我用的spring cloud是dalston版本sr1,比网上其他材料的版本要新,因为在新版本中feign对hystrix的支持默认是关闭的,所以要通过配置手动打开feign.hystrix.enabled=true,这样服务降级等功能才有效果。

application启动程序

@springbootapplication 
@enableeurekaclient 
@enablefeignclients 
public class demofeignapplication {    
  public static void main(string[] args) { 
    springapplication.run(demofeignapplication.class, args); 
  } 
}

注意这里还有个坑,我这里用的是@springbootapplication+@enableeurekaclient,而不是用的@springcloudapplication,因为后者包含了@enablecircuitbreaker,而@enablecircuitbreaker又是属于hystrix包里的内容,我的pom里并没有引入hystrix。所以这一点spring cloud做的还是有不足的地方,直接用@springcloudapplication编译不会报错,但是启动不了。当然这里的主角还是@enablefeignclients这个注解。

核心客户端代码

@feignclient(name="demo-feign-freeservice",fallback=demofeignfallback.class) 
public interface demofeignservice{ 
    
  @requestmapping(value="/feign-service/serviceget",method=requestmethod.get) 
  string helloservice(@requestparam("name") string name); 
   
  @requestmapping(value="/feign-service/servicehead", method=requestmethod.head) 
  string helloservice(@requestheader("name") string name, 
      @requestheader("password") string password); 
   
  @requestmapping(value="/feign-service/servicepost", method=requestmethod.post) 
  string helloservice(@requestbody userdemo userdemo);  
} 

@feignclient注解定义了该接口是一个feign客户端,name指定了注册到eureka上的服务名,fallback是服务降级后的接口实现类。

@requestmapping里指定了请求的相对url和http请求方式,与服务端一一对应。入参里的@requestparam、

@requestbody、@requestheader注解比起服务端多了value属性,这里不能省略,需要显式的告知feign客户端参数要如何对应。

降级服务代码:

@component 
public class demofeignfallback implements demofeignservice{ 
  @override 
  public string helloservice(string name) { 
    return "get error"; 
  } 
 
  @override 
  public string helloservice(string name,string password) { 
    return "head error"; 
  } 
   
  @override 
  public string helloservice(userdemo userdemo) { 
    return "post error"; 
  } 
} 

发现这里的入参里我故意去掉了@requestparam、@requestbody、@requestheader注解,因为这几个注解本质上的意义就在于feign在做微服务调用的时候对http传递参数用的,但服务降级根本不会做http请求了,所以此处可以省略。

controller代码:

@restcontroller 
public class demofeigncontroller {    
  @autowired 
  private demofeignservice demofeignservice;    
  @requestmapping(value="/test", method=requestmethod.get) 
  public string demoservicetest() { 
    stringbuffer sb = new stringbuffer(); 
    sb.append(demofeignservice.helloservice("yuanyuan")); 
    sb.append("\n"); 
    sb.append(demofeignservice.helloservice("yjt","xixihaha")); 
    sb.append("\n"); 
    sb.append(demofeignservice.helloservice(new userdemo("yejingtao","123456"))); 
    return sb.tostring();      
  } 
} 

我们来看效果:

Spring Cloud Feign实例讲解学习

Spring Cloud Feign实例讲解学习

我们服务都没超时,3个方法全部正常,但是head请求没有拿到返回值,这个是因为head方式http请求的特性决定的,head不返回response的body体,一般用来做连通性测试来用。

再看一组:

运气不好head和post请求方法处理时间超过了2000ms,服务降级,实现被fallback处理类取代。

Spring Cloud Feign实例讲解学习

Spring Cloud Feign实例讲解学习

在案例一中我们总有种感觉,服务提供方和服务调用方存在重复的代码,是否可以进行优化?请看案例二。

案例二:

eureka注册中心:

接口api:

服务提供方:

服务调用方:

案例二最大的变动是将服务能力单独写到一个api的project中,调用方和提供方pom都依赖这个api。

api:

public interface helloservice {    
  @requestmapping(value="/feign-service/serviceget",method=requestmethod.get) 
  string helloservice(@requestparam("name") string name); 
   
  @requestmapping(value="/feign-service/servicehead", method=requestmethod.head) 
  string helloservice(@requestheader("name") string name, 
      @requestheader("password") string password); 
   
  @requestmapping(value="/feign-service/servicepost", method=requestmethod.post) 
  string helloservice(@requestbody userdemo userdemo);    
} 

服务提供方:

@restcontroller 
public class helloservicecontorller implements helloservice{    
  private logger logger = loggerfactory.getlogger(this.getclass());    
  private void sleep(string methodname) { 
    int sleepmintime = new random().nextint(3000); 
    logger.info("helloservice "+methodname+" sleepmintime: "+sleepmintime); 
    try { 
      thread.sleep(sleepmintime); 
    } catch (interruptedexception e) { 
      e.printstacktrace(); 
    } 
  } 
   
  @override 
  public string helloservice(@requestparam("name") string name) { 
    sleep("get"); 
    return "helloserviceimpl name :"+name; 
  } 
   
  @override 
  public string helloservice(@requestheader("name") string name, 
      @requestheader("password") string password) { 
    sleep("header"); 
    return "helloservicehead name :"+name +" password:"+password; 
  } 
   
  @override 
  public string helloservice(@requestbody userdemo userdemo) { 
    sleep("post"); 
    return userdemo.tostring(); 
  }       
} 

服务调用方:

@feignclient(name="demo-feign-serviceimpl", fallback=feignservicefallback.class) 
public interface feignservice extends helloservice{  
} 

其它代码基本不变,效果也一样。

两种风格各有优缺点:freestyle的更*,服务端新增方法不会影响客户端代码,缺点是服务能力不同步服务能力的变动会引起异常;api格式服务端客户端服务能力同步,但是接口的变动需要修改两边的代码,需要构建的时候就要考虑清楚。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。