目录
一、Gateway简介
1.1 Gateway简介
Spring Cloud Gatewaycrm开发定制旨在提供一种简单而有crm开发定制效的方式来对API进行路由,crm开发定制并为他们提供切面,例如:安全性,监控/指标 和弹性等。
1.2 Gateway原理
客户端向spring-cloud-gatewaycrm开发定制请求网关映射处理程序(gateway handler mapping),crm开发定制如果确认请求与路由匹配,crm开发定制则将请求发送到web处理程序(gateway web handler),webcrm开发定制处理程序通过特定于该crm开发定制请求的过滤器链处理请求,图中filterscrm开发定制被虚线划分的原因是filterscrm开发定制可以在发送代理请求之前(pre filter)crm开发定制或之后执行逻辑(post filter)。crm开发定制先执行所有pre filter逻辑,crm开发定制然后进行请求代理。在请求代理执行完后,执行post filter逻辑。
二、Gateway程序案例
2.1 总体说明
本项目总体上为一个分步骤编写完成SpringCloud微服务开发项目的示例程序,使用了Eureka服务注册(第一步)、Ribbon请求负载均衡(第二步)和SpringCloud Gateway微服务网关(第三步)、完成统一服务接口调用(第四步)。
本案例说明为第三步:在前两步完成的基础上,引入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的,它与spring boot mvc方式不兼容,启动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.1 openfeign介绍
Spring OpenFeign是一个轻量级的http请求调用框架。基于Netflix Feign实现,整合了Spring Cloud Ribbon和Spring Cloud Hystrix。Spring OpenFeign具有可插拔注解支持,包含Feign注解和JAX-RS注解,同时扩展了对Spring-MVC的注解支持。入参和请求都比较直观。Feign封装了HTTP调用流程,通过面向接口的方式,让微服务之间的接口调用变得简单,默认使用的是JDK的httpUrlConnection。 Feign是一个伪客户端,不会做任何的请求处理。
官方文档在这里
四、openfeign程序编写
4.1总体说明
本项目总体上为一个分步骤编写完成SpringCloud微服务开发项目的示例程序,使用了Eureka服务注册(第一步)、Ribbon请求负载均衡(第二步)和SpringCloud Gateway微服务网关(第三步)、Feign完成统一服务接口调用(第四步)
本案例说明为第四步:在前三步完成的基础上,引入Open Feign,通过在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.3 application.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”:
- package com.tjetc.controller;
-
- import com.alibaba.fastjson.JSONObject;
- import com.tjetc.feign.ServiceOneFeign;
- import com.tjetc.feign.ServiceThreeFeign;
- import com.tjetc.feign.ServiceTwoFeign;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RestController;
-
- @RestController
- @RequestMapping("unifrom")
- public class FeignUniformController {
- @Autowired
- private ServiceOneFeign serviceOneFeign;
- @Autowired
- private ServiceTwoFeign serviceTwoFeign;
- @Autowired
- private ServiceThreeFeign serviceThreeFeign;
-
- @RequestMapping("serviceOne")
- public JSONObject serviceOne() {
- JSONObject serviceOneJson = serviceOneFeign.serviceOne();
- return serviceOneJson;
- }
-
- @RequestMapping("serviceTwo")
- public JSONObject serviceTwo() {
- JSONObject serviceTwoJson = serviceTwoFeign.serviceTwo();
- return serviceTwoJson;
- }
-
- @RequestMapping("serviceThree")
- public JSONObject serviceThree() {
- JSONObject serviceThreeJson = serviceThreeFeign.serviceThree();
- return serviceThreeJson;
- }
-
- @RequestMapping("serviceThree_toOne")
- public JSONObject serviceThreeToOne() {
- JSONObject serviceThreeToOneJson = serviceThreeFeign.serviceThreeToOne();
- return serviceThreeToOneJson;
- }
-
- @RequestMapping("serviceThree_toTwo")
- public JSONObject serviceThreeToTwo() {
- JSONObject serviceThreeToTwoJson = serviceThreeFeign.serviceThreeToTwo();
- return serviceThreeToTwoJson;
- }
- }
注意1:要把ServiceThree项目中api使用服务调用
注意2:由于springcloud的Gateway使用openfeign有错误,需要修正代码如下:
- package com.tjetc.configuration;
-
- import org.springframework.cloud.client.ServiceInstance;
- import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
- import org.springframework.cloud.client.loadbalancer.Request;
- import org.springframework.cloud.client.loadbalancer.Response;
- import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
- import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
- import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
- import reactor.core.publisher.Mono;
-
- import java.util.concurrent.CompletableFuture;
- import java.util.concurrent.ExecutionException;
-
-
- public class CustomBlockingLoadBalancerClient extends BlockingLoadBalancerClient {
- private final ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerClientFactory;
-
-
- public CustomBlockingLoadBalancerClient(LoadBalancerClientFactory loadBalancerClientFactory,
- LoadBalancerProperties properties) {
- super(loadBalancerClientFactory, properties);
- this.loadBalancerClientFactory = loadBalancerClientFactory;
- }
-
- public CustomBlockingLoadBalancerClient(ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerClientFactory) {
- super(loadBalancerClientFactory);
- this.loadBalancerClientFactory = loadBalancerClientFactory;
- }
-
-
- @Override
- public <T> ServiceInstance choose(String serviceId, Request<T> request) {
- ReactiveLoadBalancer<ServiceInstance> loadBalancer =
- loadBalancerClientFactory.getInstance(serviceId);
- if (loadBalancer == null) {
- return null;
- }
- CompletableFuture<Response<ServiceInstance>> f =
- CompletableFuture.supplyAsync(() -> {
- Response<ServiceInstance> loadBalancerResponse =
- Mono.from(loadBalancer.choose(request)).block();
- return loadBalancerResponse;
- });
- Response<ServiceInstance> loadBalancerResponse = null;
- try {
- loadBalancerResponse = f.get();
- } catch (InterruptedException e) {
- e.printStackTrace();
- } catch (ExecutionException e) {
- e.printStackTrace();
- }
- if (loadBalancerResponse == null) {
- return null;
- }
- return loadBalancerResponse.getServer();
- }
- }
- package com.tjetc.configuration;
-
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
- import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
-
- @Configuration
- public class LoadBalancerClientConfig {
- @Autowired
- private LoadBalancerClientFactory loadBalancerClientFactory;
-
- @Bean
- public LoadBalancerClient blockingLoadBalancerClient() {
- return new CustomBlockingLoadBalancerClient(loadBalancerClientFactory);
- }
- }
- package com.tjetc.configuration;
-
- import feign.codec.Decoder;
- import org.springframework.beans.BeansException;
- import org.springframework.beans.factory.ObjectFactory;
- import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
- import org.springframework.cloud.openfeign.support.ResponseEntityDecoder;
- import org.springframework.cloud.openfeign.support.SpringDecoder;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.http.MediaType;
- import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
-
- import java.util.ArrayList;
- import java.util.List;
-
- @Configuration
- public class FeignConfig {
- @Bean
- public Decoder feignDecoder() {
- return new ResponseEntityDecoder(new SpringDecoder(feignHttpMessageConverter()));
- }
-
- public ObjectFactory<HttpMessageConverters> feignHttpMessageConverter() {
- final HttpMessageConverters httpMessageConverters = new HttpMessageConverters(new GateWayMappingJackson2HttpMessageConverter());
- return new ObjectFactory<HttpMessageConverters>() {
- @Override
- public HttpMessageConverters getObject() throws BeansException {
- return httpMessageConverters;
- }
- };
- }
-
- public class GateWayMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {
- GateWayMappingJackson2HttpMessageConverter() {
- List<MediaType> mediaTypes = new ArrayList<>();
- 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
外部系统再也无须获知内部的微服务信息了,从而做到了统一服务接口的封装(内部负载均衡)!