目录一、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{@AutowiredprivateServiceOneFeignserviceOneFeign;@AutowiredprivateServiceTwoFeignserviceTwoFeign;@AutowiredprivateServiceThreeFeignserviceThreeFeign;@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;}@Overridepublic<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;@ConfigurationpublicclassLoadBalancerClientConfig{@AutowiredprivateLoadBalancerClientFactoryloadBalancerClientFactory;@BeanpublicLoadBalancerClientblockingLoadBalancerClient(){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;@ConfigurationpublicclassFeignConfig{@BeanpublicDecoderfeignDecoder(){returnnewResponseEntityDecoder(newSpringDecoder(feignHttpMessageConverter()));}publicObjectFactory<HttpMessageConverters>feignHttpMessageConverter(){finalHttpMessageConvertershttpMessageConverters=newHttpMessageConverters(newGateWayMappingJackson2HttpMessageConverter());returnnewObjectFactory<HttpMessageConverters>(){@OverridepublicHttpMessageConvertersgetObject()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
外部系统再也无须获知内部的微服务信息了,从而做到了统一服务接口的封装(内部负载均衡)!