当前位置:

SpringCloud-Gateway Feign

访客 2024-01-05 1226 0

目录一、Gateway简介1.1Gateway简介1.2Gateway原理二、Gateway程序案例2.1总体说明2.2新建SpringCloud-Gateway子项目2.3结果验证2.4总结三、openfeign简介3.1openfeign介绍四、openfeign程序编写4.1总体说明4.2修改pom.xml引入OpenFeign的依赖4.3application.yml不做变化4.4修改主类,启用Feign4.5编写Feign接口4.6提供统一对外服务接口4.7结果验证4.8总结

一、Gateway简介

1.1Gateway简介

SpringCloudGateway旨在提供一种简单而有效的方式来对API进行路由,并为他们提供切面,例如:安全性,监控/指标和弹性等。

1.2Gateway原理

客户端向spring-cloud-gateway请求网关映射处理程序(gatewayhandlermapping),如果确认请求与路由匹配,则将请求发送到web处理程序(gatewaywebhandler),web处理程序通过特定于该请求的过滤器链处理请求,图中filters被虚线划分的原因是filters可以在发送代理请求之前(prefilter)或之后执行逻辑(postfilter)。先执行所有prefilter逻辑,然后进行请求代理。在请求代理执行完后,执行postfilter逻辑。

二、Gateway程序案例

2.1总体说明

本项目总体上为一个分步骤编写完成SpringCloud微服务开发项目的示例程序,使用了Eureka服务注册(第一步)、Ribbon请求负载均衡(第二步)和SpringCloudGateway微服务网关(第三步)、Feign完成统一服务接口调用(第四步)。
本案例说明为第三步:在前两步完成的基础上,引入SpringCloud-Gateway,通过网关统一调用服务。
本案例在第二步案例基础上,原有项目不做改变,只是新建了Gateway子项目,有变化的内容如图:

注意:由于SpringCloud-Gateway是基于webflux的,它跟传统的springboot-mvc是冲突的,因此不需在其中排除springmvc相关包!

2.2新建SpringCloud-Gateway子项目

1.在父项目中,新建子模块项目:new->Module依然选择maven项目。

本项目名称我们命名为Gateway。

2.配置pom.xml

依赖项配置如下:

  • <dependencies>
  • <!--Eureka客户端-->
  • <dependency>
  • <groupId>org.springframework.cloud</groupId>
  • <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  • </dependency>
  • <!--SpringCloud网关Gateway-->
  • <dependency>
  • <groupId>org.springframework.cloud</groupId>
  • <artifactId>spring-cloud-starter-gateway</artifactId>
  • </dependency>
  • <dependency>
  • <groupId>com.alibaba</groupId>
  • <artifactId>fastjson</artifactId>
  • </dependency>
  • </dependencies>
  • 注意:由于SpringCloud-Gateway是基于webflux的,它与springbootmvc方式不兼容,启动Gateway会报错,因此不要引入spring-boot-starter-web依赖!

    3.配置application.yml文件

    Gateway项目也是一个Eureka客户端,并且需要SpringCloud-Gateway自身的配置:

    4.编写SpringBoot应用主类

    2.3结果验证

    1.按顺序启动各个子项目

    先启动EurekaService子项目,它是Eureka服务器,其他子模块都需作为Eureka客户端在它上面进行注册!
    再启动Gateway、ServiceOne、ServiceOneCopy、ServiceTwo和ServiceThree,启动后如下图:

    2.进入Eureka服务状态页面,查看服务注册情况

    在谷歌浏览器中输入:http://127.0.0.1:8000/,进入Eureka服务页面,我们查看当前注册情况:

    3.验证已有的各业务接口

    在使用Gateway之后,所有的请求都应当通过Gateway来统一调用各个微服务业务模块的接口,根据Gateway的配置,调用形式统一为:
    http://Gateway_HOST:Gateway_PORT/大写的serviceId/**
    我们在浏览器地址栏或者PostMan中依次请求已存在的几个接口地址:

    1)ServiceOne模块业务接口

    http://127.0.0.1:8088/SERVICE-ONE/serviceOne

    由于ServiceOne和ServiceOneCopy都使用同一个应用ID:“service-one”,因此Gateway通过应用ID请求“serviceOne”接口时,已经自动进行了负载均衡,第二次重复调用的结果:

    2)ServiceTwo模块业务接口

    http://127.0.0.1:8088/SERVICE-TWO/serviceTwo

    3)ServiceThree模块业务接口(负载均衡)

    http://127.0.0.1:8088/SERVICE-THREE/serviceThree

    http://127.0.0.1:8088/SERVICE-THREE/serviceThree_toOne

    第一次调用:

    第二次调用:

    http://127.0.0.1:8088/SERVICE-THREE/serviceThree_toTwo

    2.4总结

    结论:通过Gateway作为服务网关,可统一调用其他微服务提供的业务接口!

    且Gateway会自动对映射为同一服务名称(应用ID)的模块中的相同接口进行负载均衡。

    另外,目前还可以通过各微服务子模块的具体业务地址,继续访问它的业务接口。这是因为SpringCloud-Gateway对于各子业务模块是“无感知”、“透明”的。将来需要在各模块添加认证拦截功能,可以保证未经认证的请求不能直接进入各微服务接口!(也可以通过运维手段只开放Gateway端口,其他微服务端口全部为内网端口)

    三、openfeign简介

    3.1openfeign介绍

    SpringOpenFeign是一个轻量级的http请求调用框架。基于NetflixFeign实现,整合了SpringCloudRibbon和SpringCloudHystrix。SpringOpenFeign具有可插拔注解支持,包含Feign注解和JAX-RS注解,同时扩展了对Spring-MVC的注解支持。入参和请求都比较直观。Feign封装了HTTP调用流程,通过面向接口的方式,让微服务之间的接口调用变得简单,默认使用的是JDK的httpUrlConnection。Feign是一个伪客户端,不会做任何的请求处理。

    官方文档在这里SpringCloudOpenFeign

    四、openfeign程序编写

    4.1总体说明

    本项目总体上为一个分步骤编写完成SpringCloud微服务开发项目的示例程序,使用了Eureka服务注册(第一步)、Ribbon请求负载均衡(第二步)和SpringCloudGateway微服务网关(第三步)、Feign完成统一服务接口调用(第四步)
    本案例说明为第四步:在前三步完成的基础上,引入OpenFeign,通过在Gateway网关中统一调用服务接口(请求时不再出现各微服务名称)。
    本案例在第三步案例基础上,原有项目不做改变,只是在Gateway子项目中引入OpenFeign并作相应配置,有变化的内容如图:

    4.2修改pom.xml引入OpenFeign的依赖

    我们在原有Gateway子项目的pom.xml文件中,引入OpenFeign的依赖:

  • <!--feign依赖包-->
  • <dependency>
  • <groupId>org.springframework.cloud</groupId>
  • <artifactId>spring-cloud-starter-openfeign</artifactId>
  • </dependency>
  • 4.3application.yml不做变化

    本次使用Feign,无需改变application.yml配置文件!

    4.4修改主类,启用Feign

    接下来,我们需要在SpringBootApplication的主类中启用Feign,给主类前添加“@EnableFeignClients”。

    4.5编写Feign接口

    在Gateway项目增加feign包,再接下来,我们需要在项目feign包内编写要封装(内部调用其它微服务)的接口。

    1.封装“SERVICE-ONE”接口

    新增“ServiceOneFeign.java”文件,编写接口:

    2.封装“SERVICE-TWO”接口

    新增“ServiceTwoFeign.java”文件,编写接口:

    3.封装“SERVICE-THREE”接口

    新增“ServiceThreeFeign.java”文件,编写接口:

    4.6提供统一对外服务接口

    最后,在Gateway中,编写对外统一提供服务的Controller接口,从此刻起外部只需要同意调用这些“统一形式的服务接口”即可,无需关心具体的微服务子模块了!
    我们新增一个控制器“FeignUniformController”:

  • packagecom.tjetc.controller;
  • importcom.alibaba.fastjson.JSONObject;
  • importcom.tjetc.feign.ServiceOneFeign;
  • importcom.tjetc.feign.ServiceThreeFeign;
  • importcom.tjetc.feign.ServiceTwoFeign;
  • importorg.springframework.beans.factory.annotation.Autowired;
  • importorg.springframework.web.bind.annotation.RequestMapping;
  • importorg.springframework.web.bind.annotation.RestController;
  • @RestController
  • @RequestMapping("unifrom")
  • publicclassFeignUniformController{
  • @Autowired
  • privateServiceOneFeignserviceOneFeign;
  • @Autowired
  • privateServiceTwoFeignserviceTwoFeign;
  • @Autowired
  • privateServiceThreeFeignserviceThreeFeign;
  • @RequestMapping("serviceOne")
  • publicJSONObjectserviceOne(){
  • JSONObjectserviceOneJson=serviceOneFeign.serviceOne();
  • returnserviceOneJson;
  • }
  • @RequestMapping("serviceTwo")
  • publicJSONObjectserviceTwo(){
  • JSONObjectserviceTwoJson=serviceTwoFeign.serviceTwo();
  • returnserviceTwoJson;
  • }
  • @RequestMapping("serviceThree")
  • publicJSONObjectserviceThree(){
  • JSONObjectserviceThreeJson=serviceThreeFeign.serviceThree();
  • returnserviceThreeJson;
  • }
  • @RequestMapping("serviceThree_toOne")
  • publicJSONObjectserviceThreeToOne(){
  • JSONObjectserviceThreeToOneJson=serviceThreeFeign.serviceThreeToOne();
  • returnserviceThreeToOneJson;
  • }
  • @RequestMapping("serviceThree_toTwo")
  • publicJSONObjectserviceThreeToTwo(){
  • JSONObjectserviceThreeToTwoJson=serviceThreeFeign.serviceThreeToTwo();
  • returnserviceThreeToTwoJson;
  • }
  • }
  • 注意1:要把ServiceThree项目中api使用服务调用

    注意2:由于springcloud的Gateway使用openfeign有错误,需要修正代码如下:

  • packagecom.tjetc.configuration;
  • importorg.springframework.cloud.client.ServiceInstance;
  • importorg.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
  • importorg.springframework.cloud.client.loadbalancer.Request;
  • importorg.springframework.cloud.client.loadbalancer.Response;
  • importorg.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
  • importorg.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
  • importorg.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
  • importreactor.core.publisher.Mono;
  • importjava.util.concurrent.CompletableFuture;
  • importjava.util.concurrent.ExecutionException;
  • publicclassCustomBlockingLoadBalancerClientextendsBlockingLoadBalancerClient{
  • privatefinalReactiveLoadBalancer.Factory<ServiceInstance>loadBalancerClientFactory;
  • publicCustomBlockingLoadBalancerClient(LoadBalancerClientFactoryloadBalancerClientFactory,
  • LoadBalancerPropertiesproperties){
  • super(loadBalancerClientFactory,properties);
  • this.loadBalancerClientFactory=loadBalancerClientFactory;
  • }
  • publicCustomBlockingLoadBalancerClient(ReactiveLoadBalancer.Factory<ServiceInstance>loadBalancerClientFactory){
  • super(loadBalancerClientFactory);
  • this.loadBalancerClientFactory=loadBalancerClientFactory;
  • }
  • @Override
  • public<T>ServiceInstancechoose(StringserviceId,Request<T>request){
  • ReactiveLoadBalancer<ServiceInstance>loadBalancer=
  • loadBalancerClientFactory.getInstance(serviceId);
  • if(loadBalancer==null){
  • returnnull;
  • }
  • CompletableFuture<Response<ServiceInstance>>f=
  • CompletableFuture.supplyAsync(()->{
  • Response<ServiceInstance>loadBalancerResponse=
  • Mono.from(loadBalancer.choose(request)).block();
  • returnloadBalancerResponse;
  • });
  • Response<ServiceInstance>loadBalancerResponse=null;
  • try{
  • loadBalancerResponse=f.get();
  • }catch(InterruptedExceptione){
  • e.printStackTrace();
  • }catch(ExecutionExceptione){
  • e.printStackTrace();
  • }
  • if(loadBalancerResponse==null){
  • returnnull;
  • }
  • returnloadBalancerResponse.getServer();
  • }
  • }
  • packagecom.tjetc.configuration;
  • importorg.springframework.beans.factory.annotation.Autowired;
  • importorg.springframework.cloud.client.loadbalancer.LoadBalancerClient;
  • importorg.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
  • importorg.springframework.context.annotation.Bean;
  • importorg.springframework.context.annotation.Configuration;
  • @Configuration
  • publicclassLoadBalancerClientConfig{
  • @Autowired
  • privateLoadBalancerClientFactoryloadBalancerClientFactory;
  • @Bean
  • publicLoadBalancerClientblockingLoadBalancerClient(){
  • returnnewCustomBlockingLoadBalancerClient(loadBalancerClientFactory);
  • }
  • }
  • packagecom.tjetc.configuration;
  • importfeign.codec.Decoder;
  • importorg.springframework.beans.BeansException;
  • importorg.springframework.beans.factory.ObjectFactory;
  • importorg.springframework.boot.autoconfigure.http.HttpMessageConverters;
  • importorg.springframework.cloud.openfeign.support.ResponseEntityDecoder;
  • importorg.springframework.cloud.openfeign.support.SpringDecoder;
  • importorg.springframework.context.annotation.Bean;
  • importorg.springframework.context.annotation.Configuration;
  • importorg.springframework.http.MediaType;
  • importorg.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
  • importjava.util.ArrayList;
  • importjava.util.List;
  • @Configuration
  • publicclassFeignConfig{
  • @Bean
  • publicDecoderfeignDecoder(){
  • returnnewResponseEntityDecoder(newSpringDecoder(feignHttpMessageConverter()));
  • }
  • publicObjectFactory<HttpMessageConverters>feignHttpMessageConverter(){
  • finalHttpMessageConvertershttpMessageConverters=newHttpMessageConverters(newGateWayMappingJackson2HttpMessageConverter());
  • returnnewObjectFactory<HttpMessageConverters>(){
  • @Override
  • publicHttpMessageConvertersgetObject()throwsBeansException{
  • returnhttpMessageConverters;
  • }
  • };
  • }
  • publicclassGateWayMappingJackson2HttpMessageConverterextendsMappingJackson2HttpMessageConverter{
  • GateWayMappingJackson2HttpMessageConverter(){
  • List<MediaType>mediaTypes=newArrayList<>();
  • mediaTypes.add(MediaType.valueOf(MediaType.TEXT_HTML_VALUE";charset=UTF-8"));
  • setSupportedMediaTypes(mediaTypes);
  • }
  • }
  • }
  • 4.7结果验证

    1.按顺序启动各个子项目

    先启动EurekaService子项目,它是Eureka服务器,其他子模块都需作为Eureka客户端在它上面进行注册!
    再启动Gateway、ServiceOne、ServiceOneCopy、ServiceTwo和ServiceThree,启动后如下图:

    2.进入Eureka服务状态页面,查看服务注册情况

    在谷歌浏览器中输入:http://127.0.0.1:8000/,进入Eureka服务页面,我们查看当前注册情况:

    3.验证新增统一服务接口

    在引入Feign后,我们需要调用Gateway的统一服务接口,形式为:
    http://Gateway_HOST:Gateway_PORT/feignUniform/**
    我们在浏览器地址栏或者PostMan中依次请求封装好的几个接口地址:

    1)unifrom/serviceOne(ServiceOne模块)业务接口

    http://127.0.0.1:8088/unifrom/serviceOne

    由于ServiceOne和ServiceOneCopy都使用同一个应用ID:“service-one”,因此Gateway通过封装的请求“serviceOne”接口时,已经自动进行了负载均衡,第二次重复调用的结果:

    2)feignUniform/serviceTwo(ServiceTwo模块)业务接口

    http://127.0.0.1:8088/unifrom/serviceTwo

    3)feignUniform/serviceThree_toOne(ServiceThree模块,负载均衡)

    http://127.0.0.1:8088/unifrom/serviceThree_toOne

    第一次调用:

    第二次调用:

    4)http://127.0.0.1:8088/unifrom/serviceThree_toTwo

    4.8总结

    结论:通过Gateway作为服务网关,我们已经做到统一调用其他微服务提供的业务接口。但需要在请求接口路径上添加各业务模块的应用名称,比如:
    http://127.0.0.1:8088/SERVICE-ONE/serviceOne
    http://127.0.0.1:8088/SERVICE-TWO/serviceTwo

    在引入了Feign后,可以做到对外统一服务接口形式,因此暴露给外部的接口地址变为:
    http://127.0.0.1:8088/feignUniform/serviceOne
    http://127.0.0.1:8088/feignUniform/serviceTwo

    外部系统再也无须获知内部的微服务信息了,从而做到了统一服务接口的封装(内部负载均衡)!

    发表评论

    • 评论列表
    还没有人评论,快来抢沙发吧~