OpenFeign是⼀种声明式,模版化的HTTP客户端使⽤OpenFeign进⾏远程调⽤时,开发者完全感知不到这是在进⾏远程调⽤,⽽是像在调⽤本地⽅法⼀样使⽤⽅式是注解 接⼝形式,把需要调⽤的远程接⼝封装到接⼝当中,映射地址为远程接⼝的地址在启动SpringCloud应⽤时,Feign会扫描标有@FeignClient注解的接⼝,⽣成代理并且注册到Spring容器当中⽣成代理时Feign会为每个接⼝⽅法创建⼀个RequestTemplate对象,该对象封装HTTP请求需要的全部信息,请求参数名、请求⽅法等信息都是在这个过程中确定的,模版化就体现在这⾥,我来为大家科普一下关于feign跨服务调用效率低?下面希望有你要的答案,我们一起来看看吧!

feign跨服务调用效率低(OpenFeign入门以及远程调用)

feign跨服务调用效率低

一、OpenFeign介绍

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

二、OpenFeign的使用

<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.使用注册中心

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 //开启服务调⽤,只需要在调⽤⽅开启即可

@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.使用配置中心

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

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

四、补充

//使⽤配置中⼼拿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连接复⽤能⼒,需要通过引⼊依赖来使⽤

,