当前位置:首页 > 技术分析 > 正文内容

「Feign」OpenFeign入门以及远程调用

ruisui883个月前 (02-03)技术分析17

一、OpenFeign介绍

OpenFeign是一种声明式,模版化的HTTP客户端。使用OpenFeign进行远程调用时,开发者完全感知不到这是在进行远程调用,而是像在调用本地方法一样。使用方式是注解+接口形式,把需要调用的远程接口封装到接口当中,映射地址为远程接口的地址。在启动SpringCloud应用时,Feign会扫描标有@FeignClient注解的接口,生成代理并且注册到Spring容器当中。生成代理时Feign会为每个接口方法创建一个RequestTemplate对象,该对象封装HTTP请求需要的全部信息,请求参数名、请求方法等信息都是在这个过程中确定的,模版化就体现在这里。

二、OpenFeign的使用

  • 搭建前置环境,在pom.xml文件中引入依赖,可以选择使用注册中心或者配置中心
<dependencyManagement>
 <dependencies>
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-dependencies</artifactId>
 <version>2020.0.3</version>
 <type>pom</type>
 <scope>import</scope>
 </dependency>
 </dependencies>
</dependencyManagement>
<dependencies>
 <!-- 配置中心依赖-->
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-consul-config</artifactId>
 </dependency>
 <!-- 注册中心依赖 -->
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-consul-discovery</artifactId>
 </dependency>
 <!-- 健康检查,将服务注册到consul需要 -->
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-actuator</artifactId>
 </dependency>
 <!-- openfeign,在需要远程调用的服务中引入 -->
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-openfeign</artifactId>
 </dependency>
</dependencies>

1.使用注册中心

  • 使用注册中心,将服务注册到consul(nacos),调用者拿到被调用服务的地址端口进行调用
spring.cloud.consul.host=192.168.0.124		#consul地址
spring.cloud.consul.port=8080		#端口号
spring.cloud.consul.discovery.service-name=service-test-01		#服务名称
spring.cloud.consul.discovery.health-check-interval=1m		#健康检查间隔时间
server.port=10000		#服务端口号
  • 在配置类上开启服务发现以及允许远程调用
@EnableDiscoveryClient //开启服务发现
@EnableFeignClients //开启服务调用,只需要在调用方开启即可
  • 服务运行之后可以在consul的UI界面看到运行的服务,consul会定时检查服务的健康状态
  • 创建远程调用接口
@FeignClient("serviceName")
public interface Service2Remote {
 /** 这里有自定义解码器对远程调用的结果进行解析,拿到真正的返回类型,所以接口返回值类型和远程接口返回类型保持一致 **/
 @PostMapping("/page")
 List<QuestionResp> pageQuestion(PageQuestionReq req);
}
  • 简单使用
@RestController
@RequestMapping("/service/remote")
public class RemoteController {
 @Autowired
 private Service2Remote service2Remote;
 @PostMapping("/getQuestionList")
 public List<QuestionResp> getQuestionList(@RequestBody PageQuestionReq req){
 List<QuestionResp> result = service2Remote.pageQuestion(req);
 //对拿到的数据进行处理...
 return result;
 }
}

2.使用配置中心

  • 将请求的URL写在配置中心进行读取修改配置文件
spring.cloud.consul.config.format=KEY_VALUE	#consul支持yaml格式和Key-value形式
spring.cloud.consul.config.enabled=true	#开启配置
spring.cloud.consul.config.prefixes=glab/plat/wt/application/test	#consul配置存放的外层文件夹目录
spring.cloud.consul.config.default-context=config	#父级文件夹
spring.cloud.consul.config.watch.delay=1000	#轮询时间
spring.cloud.consul.discovery.enabled=false	#关闭注册
remote.url=www.baidu.com	#请求地址
  • 创建远程调用接口
@FeignClient(name = "service2RemoteByUrl",url = "${remote.url}") //name需要配置,URL从配置中心读取
public interface Service2RemoteByUrl {
 @PostMapping("/page")
 List<QuestionResp> pageQuestion(PageQuestionReq req);
}

3.自定义解码器(编码器)

//自定义解码器实现Decoder接口,重写decode方法即可,根据具体需求进行编写
//如果是自定义编码器,需要实现Encoder接口,重写encode方法
public class FeignDecoder implements Decoder {
 @Override
 public Object decode(Response response, Type type) throws IOException,DecodeException, FeignException {
    if (response.body() == null){
        throw new DecodeException(ErrorEnum.EXECUTE_ERR.getErrno(),"没有获取到有效结果值",response.request());
    }
    // 拿到值
    String result = Util.toString(response.body().asReader(Util.UTF_8));
    Map<String,Object> resMap = null;
    try {
        resMap = JSON.parseObject(result, Map.class);
    } catch (Exception e) {
        //返回结果是字符串
        return result;
    }
}

4.远程调用携带Cookie

  • 由于feign调用是新创建一个Request,因此在请求时不会携带一些原本就有的信息,例如Cookie,因此需要自定义RequestInterceptor对Request进行额外设置,一般情况下,写入Cookie是比较常的做法,如下设置
@Configuration
public class BeanConfig {
   @Bean
   public RequestInterceptor requestInterceptor(){
      return template -> {
      ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
      HttpServletRequest request = attributes.getRequest();
      //此处可以根据业务而具体定制携带规则
      String data = request.getParameter("data");
      String code = null;
      try {
         //这里需要转码,否则会报错
         code = URLEncoder.encode(data, "UTF-8");
      } catch (UnsupportedEncodingException e) {
         e.printStackTrace();
      }
      template.query("data",code);
      //请求头中携带Cookie
      String cookie = request.getHeader("Cookie");
      template.header("Cookie",cookie);
      };
      }
   @Bean
   public Decoder decoder(){
      return new FeignDecoder();
   }
}

三、调用流程解析

//在使用EnableFeignClients开启feign功能时,点击进入会看到该注解是通过ImportFeignClientsRegistrar类生效的,其中有个方法
//registerBeanDefinitions执行两条语句
registerDefaultConfiguration(metadata, registry); //加载默认配置信息
registerFeignClients(metadata, registry); //注册扫描标有FeignClient的接口
//关注registerFeignClients方法
for (String basePackage : basePackages) {
    candidateComponents.addAll(scanner.findCandidateComponents(basePackage)); //在basePackage路径下扫描并添加标有FeignClient的接口
}
for (BeanDefinition candidateComponent : candidateComponents) { //遍历
    if (candidateComponent instanceof AnnotatedBeanDefinition) {
        registerClientConfiguration(registry, name, attributes.get("configuration")); //
        registerFeignClient(registry, annotationMetadata, attributes); //注册到Spring容器当中,方法详细在FeignClientsRegistrar类当中
    }
}
//在对feign调用时进行断点调试
//在生成Feign远程接口的代理类时,调用处理器是Feign提供的FeignInvocationHandler
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if ("equals".equals(method.getName())) {
    //equals,hashCode,toString三个方法直接本地执行
    } else if ("hashCode".equals(method.getName())) {
        return hashCode();
    } else if ("toString".equals(method.getName())) {
        return toString();
    }
    //执行方法对应的方法处理器MethodHandler,这个接口是Feign提供的,与InvocationHandler无任何关系,只有一个invoke方法
    return dispatch.get(method).invoke(args);
}
//点进上面的invoke方法
public Object invoke(Object[] argv) throws Throwable {
    //创建一个request模版
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    while (true) {
        try {
            return executeAndDecode(template, options); //创建request执行并且解码
        }
    }
}
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    Request request = targetRequest(template); //创建Request并增强
    Response response = client.execute(request, options); //执行调用请求,不再继续分析了
    response = response.toBuilder().request(request).requestTemplate(template).build();
    //如果有重写解码器,使用自定义的解码器,feign默认使用SpringEncoder
    if (decoder != null)
        return decoder.decode(response, metadata.returnType());
    }
    Request targetRequest(RequestTemplate template) {
    //如果自定义了RequestInterceptor,在这里可以对Request进行增强
    for (RequestInterceptor interceptor : requestInterceptors) {
        //执行自定义的apply方法
        interceptor.apply(template);
    }
    //创建Request
    return target.apply(template);
}

四、补充

  • 关于Client接口的实现类,使用注册中心和使用配置中心其流程稍有区别
//使用配置中心拿url方式进行调用,使用的是Client的默认内部实现类 Default ,其中Default使用的是HttpURLConnection进行Http请求的
HttpURLConnection connection = convertAndSend(request, options);
//如果使用的是服务发现,使用的使用Client的实现类FeignBlockingLoadBalancerClient,它会去根据配置的服务名去注册中心查找服务的IP地址和端口号,执行使用的仍然是默认实现类Default,通过HttpURLConnection请求
//FeignBlockingLoadBalancerClient,根据服务名称查找服务IP地址、端口 88行
ServiceInstance instance = loadBalancerClient.choose(serviceId, lbRequest);
//具体实现方法,BlockingLoadBalancerClient类中 145行
Response<ServiceInstance> loadBalancerResponse = Mono.from(loadBalancer.choose(request)).block();
//还有其他实现Client接口的客户端,例如ApacheHttpClient,ApacheHttpClient带有连接池功能,具有优秀的HTTP连接复用能力,需要通过引入依赖来使用

扫描二维码推送至手机访问。

版权声明:本文由ruisui88发布,如需转载请注明出处。

本文链接:http://www.ruisui88.com/post/1161.html

标签: feign 使用
分享给朋友:

“「Feign」OpenFeign入门以及远程调用” 的相关文章

Excel VBA 收费单据打印/一步一步带你设计【收费管理系统】12

本文于2023年6月10日首发于本人同名公众号:Excel活学活用,更多文章案例请搜索关注!☆本期内容概要☆用户窗体设置:收费结算模块设置(7)单据打印大家好,我是冷水泡茶,前几期我们分享了【收费管理系统】的设计,最近一期是(Excel VBA 收费结算模块/一步一步带你设计【收费管理系统】11),...

细数5款国外热门Linux发行版

Linux系统已经与我们的生活息息相关,当你用Android手机浏览这篇文章时,你就已经在使用Linux系统。当然作为编程开发最热门的系统,他还有很多专注于开发使用的版本。Fedora热门入门推荐,一款优秀的程序猿专供Linux发行版,自带开发者门户,集成大量教程指南、开发集成环境、虚拟机等工具,简...

Linux 最主要的发行分支

Linux 有数百个发行分支。主要的有以下四个。slackwareSlackware 是由 Patrick Volkerding 在 1992 年推出的,它是全球现存最古老的 Linux 发行版。Slackware 被设计为高度可定制和强大的,并且要求用户了解 每个元素,它的包系统是不支持依赖的。...

代码管理-9-gitlab的使用和设置

gitlab使用1、外观设置完成后保存,返回登录页面查看关于注册,有些公司是不允许打开的,,有些人数非常多的公司就需要打开注册的功能,让人员自己注册,我们来给他特定的权限就可以,毕竟人非常多的时候还由我们来给她们注册就非常不现实了,工作量会很大2、自动注册3、组&用户&项目创建组设置组名称、描述等创...

HTML5+眼球追踪?黑科技颠覆传统手机体验

今天,iH5工具推出一个新的神秘功能——眼动追踪,可以通过摄像头捕捉观众眼球活动!为了给大家具体演示该功能的使用,我做了一个案例,供大家参考。实际效果如下:案例比较简单,就是通过眼动功能获取视觉焦点位置,剔除用户看中的牌。现在,舞台的属性中多了一个“启用眼动”的选项,另外,还多了一个“启用摄像头”的...

一起学Vue:路由(vue-router)

前言学习vue-router就要先了解路由是什么?前端路由的实现原理?vue-router如何使用?等等这些问题,就是本篇要探讨的主要问题。vue-router是什么路由是什么?大概有两种说法:从路由的用途上来解释路由就是指随着浏览器地址栏的变化,展示给用户的页面也不相同。从路由的实现原理上来解释路...