1.SpringCloud介绍
1.1 SpringCloud基本概念
Spring cloud是一个基于Spring Boot实现的服务治理工具包
,用于微服务架构中管理和协调服务的
。Spring Cloud是一系列框架的有序集合。
1.2. SpringCloud常用组件
1. Netflix Eureka
Eureka就是用来管理微服务的通信地址清单的,有了Eureka之后我们通过服务的名字就能实现服务的调用。
2. Netflix Ribbon\Feign : 客户端负载均衡
简单理解就是用来解决微服务之间的通信问题。
3. Netflix Hystrix :断路器
就是用来解决微服务故障,保护微服务安全的组件。
4. Netflix Zuul : 服务网关
所有的请求都需要经过zuul之后才能到达目标服务,可以把微服务公共的是事情交给zuul统一处理,如:用户鉴权,请求监控等。
5. Spring Cloud Config :分布式配置
就是用来统一的管理服务的配置文件。
6. Spring Cloud Bus : 消息总线
消息总线是在微服务中给各个微服务广播消息的一个组件,我们使用消息总线构建一个消息中心,其他微服务来接入到消息中心,当消息总线发起消息,接入的微服务都可以收到消息从而进行消费。
7. Spring Cloud sleuth :微服务链路追踪
链路追踪的作用就是来监控维护之间的调用关系,让程序员方便直观的感受到一个请求经历了哪些微服务,以及服务的请求时间,是否有异常等。
1.3 SpringCloud的版本
SpringCloud是基于SpringBoot的,所以两者的jar包都需要导入,需要注意的是SprinbCloud的版本需要和SpringBoot的版本兼容。
Release Train | Boot Version |
---|---|
Hoxton | 2.2.x |
Greenwich | 2.1.x |
Finchley | 2.0.x |
Edgware | 1.5.x |
Dalston | 1.5.x |
2.服务注册与发现
2.1 Eureka的工作原理
1.服务注册
客户端启动向服务端注册(服务名,IP,端口等),服务端维护一个服务注册表
2.服务发现
客户端30s/次拉取注册表缓存到本地,服务调用的时候,去注册表中根据服务名查找服务实例,拿到通信地址,发起HTTP请求
3.服务续约
服务续约:客户端30s/次向服务端发送心跳续约请求,报告自己的健康状态,超过3次续约失败,会被剔除
4.服务下线
微服务(EurekaClient)关闭服务前向注册中心发送下线请求,注册中心(EurekaServer)接受到下线请求负责将该服务实例从注册列表剔除
3.EurekaServer实战
3.1 搭建项目结构
3.2 父项目管理依赖
<!--公共的一些配置-->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<!--1.管理 SpringBoot的jar包-->
<!--SpringBoot-->
<parent>
<groupId> org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
</parent>
<!--2.管理 SpringCloud的jar包-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!--3.这里是所有子项目都可以用的jar包-->
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
SpringCloud的Jar包管理参照文档:4. Quick Start
3.3 搭建Eureka Server
在 springcloud-netflix-parent 父工程下搭建好子工程 springcloud-netflix-eureka
1.导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
参照文档:12. Service Discovery: Eureka Server
2.主配置类
@SpringBootApplication
@EnableEurekaServer // @EnableEurekaServer : 开启EurekaServer服务端
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class);
}
}
3.application.yml配置文件
server:
port: 10010 #端口
eureka:
instance:
hostname: localhost #主机
client: #客户端配置
registerWithEureka: false #EurekaServer自己不要注册到EurekaServer自己 ,只有EurekaClient才注册
fetchRegistry: false #EurekaServer不要拉取服务的通信地址列表 ,只有EurekaClient才拉取地址列表
serviceUrl: #注册中心的注册地址
defaultZone: http://localhost:10010/eureka/ #http://${eureka.instance.hostname}:${server.port}/eureka/
server:
enable-self-preservation: false #关闭自我保护警告
-
serviceUrl是服务注册地址,EurekaClient需要注册到EurekaServer就得跟上该地址。
-
registerWithEureka=false :禁止自己向自己注册
-
fetchRegistry=false : 禁止拉取服务注册列表
4.启动测试
浏览器访问 http://localhost:10010
4.EurekaClient实战-用户服务
4.1 导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
4.2 主配置类
@SpringBootApplication
public class UserServerApplication {
public static void main(String[] args) {
SpringApplication.run(UserServerApplication.class);
}
}
提示:主配置类通过打@EnableEurekaClient注解开启EurekaClient客户端功能,当然如果不打这个标签也能实现功能,因为导入spring-cloud-starter-netflix-eureka-client 依赖后,默认就开启了EurekaClient 。
4.3 application.yml配置
#注册到EurekaServer
eureka:
client:
serviceUrl:
defaultZone: http://localhost:10010/eureka/
instance:
prefer-ip-address: true #使用ip地址进行注册
instance-id: service-user:10030 #实例ID
spring:
application:
name: service-user
server:
port: 10030
4.4 测试EurekaClient
浏览器再次访问http://localhost:10010,就可以看到user-server服务已经被注册到EurekaServer。
5.Eureka Client实战-订单服务
5.1 导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
5.2 主配置类
@SpringBootApplication
@EnableEurekaClient
public class OrderServerApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServerApplication.class);
}
}
5.3 application.yml配置
#注册到EurekaServer
eureka:
client:
serviceUrl:
defaultZone: http://localhost:10010/eureka/
instance:
prefer-ip-address: true #使用ip地址进行注册
instance-id: service-order:10020 #实例ID
spring:
application:
name: service-order
server:
port: 10020
5.4 测试EurekaClient
启动订单服务,访问Eureka Server的监控界面:http://localhost:10010,可以看到订单服务也注册进去了。
6.RestTemplate服务通信
6.1 解决方案
用户服务需要提供User对象,我们需要为其编写Controller接口,编写相关方法返回User,订单服务需要从用户服务获取到User对象,而浏览器需要访问订单服务获取到User对象,所以订单服务也需要编写Controller接口供浏览器来调用。
我们发现不管是用户服务还是订单服务都需要用到User对象,那么是不是在用户服务和订单服务都需要创建User的模型?当然没必要,公共的东西就是要抽取,所以我们会把User对象封装在一个公共的模块 springcloud-user-common中然后让用户服务和订单服务都去依赖这个模块。
6.2 搭建公共模块
在 springcloud-netflix-pojo-user 中创建User对象如下
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Long id;
private String name;
private String note;
}
6.3 用户和订单依赖User模块
<dependency>
<groupId>com.wyj</groupId>
<artifactId>springcloud-netflix-pojo-user</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
6.4 用户服务返回User
@RestController
public class UserController {
//加载端口
@Value("${server.port}")
private int port;
@GetMapping("/user/{id}")
public User getById(@PathVariable("id")Long id){
return new User(id,"猹:" + id,"猹,port:" + port);
}
}
6.5 订单服务获取User
@RestController
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/order/{id}")
public User getById(@PathVariable("id")Long id){
return restTemplate.getForObject("http://localhost:10030/user/"+id, User.class);
}
}
在订单服务中需要使用RestTemplate调用用户服务,我们需要把RestTmplate配置成Bean方便使用
@SpringBootApplication
@EnableEurekaClient
public class OrderServerApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServerApplication.class);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
6.6 测试服务通信
浏览器访问订单服务:http://localhost:10020/order/1
7.Ribbon客户端负载均衡
7.1 为什么要Ribbon
为了防止应用出现单节点故障问题,同时为了提高应用的作业能力,我们需要对应用做集群 ,
order-server(订单服务)在向user-server(用户服务)发起调用的时候,有两个user-server(用户服务)的通信地址,这个时候就需要有一个组件帮我们做请求的分发,即:负载均衡器,而Ribbon就是一个 - 客户端负载均衡器。
消费者Order-server集成Ribbon
Ribbon集成官方文档:16. Client Side Load Balancer: Ribbon
7.2 导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
7.3 开启负载均衡
修改RestTemplate的Bean的定义方法,加上Ribbon的负载均衡注解@LoadBalanced赋予RestTemplate有负债均衡的能力。
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
7.3.1 配置负载均衡算法
@SpringBootApplication
@EnableEurekaClient
public class OrderServerApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServerApplication.class);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
// 负载均衡算法
@Bean
public RandomRule randomRule(){
return new RandomRule();
}
}
7.4 修改Controller调用方式
@RestController
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/order/{id}")
public User getById(@PathVariable("id")Long id){
return restTemplate.getForObject("http://SERVICE-USER/user/"+id, User.class);
}
}
7.5 测试Ribbon
浏览器访问订单服务:http://localhost:10020/order/1 ,发送多次请求。
8.客户端负载均衡-OpenFeign
8.1 创建工程
创建工程 springcloud-netflix-service-pay 用来集成Feign
8.1 导入依赖
<!--spring-cloud-starter-netflix-eureka-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--2.导入Feign的包-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.wyj</groupId>
<artifactId>springcloud-netflix-pojo-user</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
8.2 配置类
1.启动类
@SpringBootApplication
@EnableFeignClients // 开启Feign支持
public class PayServerApplication {
public static void main(String[] args) {
SpringApplication.run(PayServerApplication.class);
}
}
2.yml配置
#注册到EurekaServer
eureka:
client:
serviceUrl:
defaultZone: http://localhost:10010/eureka/
instance:
prefer-ip-address: true #使用ip地址进行注册
instance-id: service-user:10040 #实例ID
spring:
application:
name: service-pay
server:
port: 10040
8.3 编写Feign的客户端接口
Feign的客户端接口是用来调用微服务的
@FeignClient("service-user")
public interface UserFeignClient {
@GetMapping("/user/{id}")
User getById(@PathVariable("id")Long id);
}
@FeignClient("service-user") : service-user是用户服务的服务名字,Feign根据服务名能够在注册中心找到目标服务的通信地址
User getById(@PathVariable("id")Long id)方法就是目标服务的方法,我们在使用Feign接口时传入的参数就会作为目标服务controller方法的参数,而返回值就是目标服务controller方法的返回值
注意:服务名要一致 , url路径要一致 , 参数要一致 , 返回值类型要一致。
8.4 编写Controller使用Feign接口
@RestController
public class PayController {
@Autowired
private UserFeignClient userFeignClient;
@GetMapping("/pay/{id}")
public User getById(@PathVariable("id")Long id){
// 使用Feign调用用户服务获取User
return userFeignClient.getById(id);
}
}
这里调用UserFeignClient.getById方法,看起来是像在调用本地方法,其实该接口已经被Feign代理,我们发起调用时其实在向Feign接口配置的目标服务发起调用。
8.5 测试
通过浏览器访问service-pay的controller:http://localhost:10040/pay/1,多次请求发现依然默认使用了轮询策
8.6 理解Feign的工作原理(重要)
当程序启动,注解了@FeignClient的接口将会被扫描然后交给spring管理,Feign会为每个方法生成一个RequestTemplate,同时封装好http信息如:url,请求参数等,然后RequestTemplate生成request请求,交给http客户端,最后http客户端会交给LoadBalancerClient,使用Ribbon的负载均衡发起调用。
8.7 Feign开启日志调试
8.7.1 配置Feign日志打印内容
@Configuration
public class FeignConfiguration {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL; // 打印Feign的所有日志
}
}
8.7.2 配置日志打印级别
application.yml中配置
logging:
level:
com.wyj: debug
9.Hystrix熔断器
9.1 理解Hystrix
9.1.1 雪崩效应
在微服务中,服务的调用非常复杂的 ,一个请求往往需要很多的微服务共同完成,可能会形成很长的服务调用链,在整个服务调用链中,某一个服务发生故障会导致调用它的服务跟着异常,然后导致整个调用链调用的异常,甚至导致整个微服务瘫痪。
9.2 Hystrix介绍
Hystrix能够完美的解决分布式系统架构中打造高可用服务面临的一系列技术难题,如雪崩。
Hystrix其设计原则如下:
-
防止单个服务异常导致整个微服务故障。
-
快速失败,如果服务出现故障,服务的请求快速失败,线程不会等待。
-
服务降级,请求故障可以返回设定好的二手方案数据(兜底数据)。
-
熔断机制,防止故障的扩散,导致整个服务瘫痪。
-
服务监控,提供了Hystrix Bashboard仪表盘,实时监控熔断器状态
9.3 Hystrix的功能
1.资源隔离(流控,限流):资源隔离包括线程池隔离和信号量隔离,作用是限制调用分布式服务的资源调用,某一个调用的服务出现问题不会影响其他服务调用 ,这里可以简单的理解为:资源隔离就是限制请求数量。
(1)线程池隔离 :Hystix提供了一个线程池,让请求在新的线程中去执行(异步),限流上限就是线程池中的线程数量+队列长度,如果超出该限制就会拒绝请求,触发降级(异步,有线程切换的消耗,自带缓存队列,能应对突然流量)
(2)信号量隔离 :设置一个信号量(数字) ,请求来了会给一个计数器+1,请求走了会-1,某一个时刻,计数器达到信号量上限就会拒绝请求,触发降级(同步,没有缓冲队列)
2.熔断机制:服务多次访问失败,就会被标记为“熔断状态”,如果某个请求去访问一个熔断状态的服务,会快速失败,触发服务降级,目的:防止请求阻塞。
3.降级机制:当一个业务(请求)失败/超时,就会走预先设置好的第二种方案(也就是降级逻辑)。
10.Hystrix编码实战
10.1 OpenFeign使用Hystrix
官方文档:Spring Cloud
10.1.1 application.yml配置
feign:
hystrix:
enabled: true #开启熔断支持
10.2 Fiegn接口熔断-fallbackFactory方式
使用fallbackFactory属性,使用工厂方式指定托底
@FeignClient(value = "service-user",fallbackFactory = UserFeignClientfallbackFactory.class)
public interface UserFeignClient {
@GetMapping("/user/{id}")
User getById(@PathVariable("id")Long id);
}
10.3 编写托底类
@Component
public class UserFeignClientfallbackFactory implements FallbackFactory<UserFeignClient> {
@Override
public UserFeignClient create(Throwable throwable) {
return new UserFeignClient() {
@Override
public User getById(Long id) {
throwable.printStackTrace();
return new User(-1L,"","系统异常");
}
};
}
}
提示:注意,这里托底类需要交给Spirng管理,类上需要打 @Component 注解 ,托底类需要实现 Feign接口,复写接口中的方法作为托底方法返回托底数据。当Fiegn调用失败就会以托底方法返回的结果返回给用户。
10.4 启动测试
11.服务网关-spring cloud zuul
11.1 什么是zuul
Zuul 是netflix开源的一个API Gateway 服务器, 本质上是一个web servlet(filter)应用。Zuul 在云平台上提供动态路由(请求分发),监控,弹性,安全等边缘服务的框架。
11.2 zuul的搭建
11.3 导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
11.4 配置开启Zuul
配置类通过 @EnableZuulProxy 注解开启zuul服务功能
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class);
}
}
11.5 配置文件配置zuul
#注册到EurekaServer
eureka:
client:
serviceUrl:
defaultZone: http://localhost:10010/eureka/
instance:
prefer-ip-address: true #使用ip地址进行注册
instance-id: service-user:10050 #实例ID
spring:
application:
name: service-zuul
server:
port: 10050
zuul:
ignored-services: "*" #不准用服务名访问
prefix: "/service" #访问前缀
routes: #路由(分发请求)
service-user: "/user/**" #配置别名
service-order: "/order/**"
service-pay: "/pay/**"
提示: 我们对zuul主要做了三个配置
-
zuul.prefix : 作为统一的前缀,在浏览器访问的时候需要加上该前缀
-
zuul.ignoredServices : 忽略使用服务名方式访问服务,而是通过routes指定的路径进行访问
-
zuul.routes : 配置服务的访问路径
11.6 测试zuul
浏览器访问:http://localhost:10050/service/user/user/1
11.7 自定义zuul的Filter
11.7.1 自定义Filter
@Component
public class LoginZullFilter extends ZuulFilter {
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
// 获取请求对象
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
// 如果请求的URI包含"/login",则返回false,表示不需要过滤该请求。
if (request.getRequestURI().contains("/login")){
return false;
}
// 如果请求的URI不包含"/login",则返回true,表示需要对该请求进行过滤。
return true;
}
@Override
public Object run() throws ZuulException {
// 获取请求对象
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
// 获取响应对象
HttpServletResponse response = requestContext.getResponse();
response.setCharacterEncoding("utf-8");
response.setContentType("application/json;charset=utf-8");
String token = request.getHeader("token");
if (StringUtils.isEmpty(token)){ // 没有token
// 设置后不会再继续向后执行
requestContext.setSendZuulResponse(false);
try {
response.getWriter().print("你需要登录");
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
11.7.2 测试
浏览器访问:http://localhost:10050/service/user/user/1
12.服务网关SpringCloudGateway
12.1 Spring Cloud Gataway的核心概念
Spring Cloud Gataway有几个核心组成:
Filter(过滤器):
与zuul的过滤器类似,可以在请求发出前后进行一些业务上的处理 ,这里分为两种类型:Gataway Filter网关Filter,Global Filter全局Filter。
Route(路由):
网关配置的基本组成模块,和Zuul的路由配置模块类似。一个Route模块由一个 ID,一个目标 URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配,目标URI会被访问。
Predicate(断言):
简单理解就是处理HTTP请求的匹配规则,在什么样的请情况下才能命中资源继续访问。
12.2 Spring Cloud Gataway入门
12.2.1 创建工程导入依赖
<!--服务注册与发现-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
12.2.2 主配置类
//服务注册与发现
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayServerApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayServerApplication .class);
}
}
12.2.3 yml配置
#注册到EurekaServer
eureka:
client:
serviceUrl:
defaultZone: http://localhost:10010/eureka/
instance:
prefer-ip-address: true #使用ip地址进行注册
instance-id: service-user:10060 #实例ID
spring:
application:
name: service-gateway
cloud:
gateway:
discovery:
locator:
enabled: false #开放服务名访问方式
lower-case-service-id: true #服务名小写
routes:
- id : service-user #指定服务名
uri: lb://service-user #去注册中心找这个服务名
predicates: #断言,匹配访问的路径
- Path=/service/user/** #服务访问路径
filters:
- StripPrefix=2 #请求转发的时候会去掉 /user访问路径
server:
port: 10060
12.2.4 访问测试
浏览器访问:http://localhost:10060/service/user/user/1
12.3 Predicate断言工厂
什么是断言工程,在Spring Cloud Gateway官方文档有如下解释:
Spring Cloud Gateway将路由作为Spring WebFlux HandlerMapping基础架构的一部分进行匹配。Spring Cloud Gateway包括许多内置的路由断言工厂。所有这些断言都与HTTP请求的不同属性匹配。您可以将多个路由断言工厂与逻辑and语句结合使用。
这里不难理解,其实断言工厂就是用来判断http请求的匹配方式。
在Spring Cloud Gateway中,针对不同的场景内置的路由断言工厂,比如
-
Query Route Predicate Factory
:根据查询参数来做路由匹配 -
RemoteAddr Route Predicate Factory
:根据ip来做路由匹配 -
Header Route Predicate Factory
:根据请求头中的参数来路由匹配 -
Host Route Predicate Factory
:根据主机名来进行路由匹配 -
Method Route Predicate Factory
:根据方法来路由匹配 -
Cookie Route Predicate Factory
:根据cookie中的属性值来匹配 -
Before Route Predicate Factory
:指定时间之间才能匹配 -
After Route Predicate Factory
: 指定时间之前才能匹配 -
Weight Route Predicate Factory
: 根据权重把流量分发到不同的主机
12.4 根据path断言-Path Route Predicate Factory(重要)
spring:
application:
name: service-gateway
cloud:
gateway:
discovery:
locator:
enabled: false #开放服务名访问方式
lower-case-service-id: true #服务名小写
routes:
- id : service-user #指定服务名
uri: lb://service-user #去注册中心找这个服务名
predicates: #断言,匹配访问的路径
- Path=/service/user/** #服务访问路径
filters:
- StripPrefix=2 #请求转发的时候会去掉 /user访问路径
13.Gateway 的 Filter 过滤器(重要)
13.1 自定义Gateway Filter
在Spring Cloud Gateway自定义过滤器,过滤器需要实现GatewayFilter和Ordered这两个接口
public class RequestTimeFilter implements GatewayFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//开始时间
long startTime = new Date().getTime();
//执行完成之后
return chain.filter(exchange).then(
Mono.fromRunnable(() -> {
long endTime = new Date().getTime();
long diff = endTime - startTime;
System.out.println("当前请求" + exchange.getRequest().getURI().getPath() + ",耗时:" + diff);
})
);
}
@Override
public int getOrder() {
return 0;
}
}
提示: getOrder返回filter的优先级,越大的值优先级越低 , 在filterI方法中计算了请求的开始时间和结束时间,最后我们还需要把该Filter配置在对应的路由上,配置如下:
@Configuration
public class FilterConfig {
//配置Filter作用于那个访问规则上
@Bean
public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) {
return builder.routes().route(r -> r.path("/service/user/**")
//去掉2个前缀
.filters(f -> f.stripPrefix(2)
.filter(new RequestTimeFilter())
.addResponseHeader("X-Response-test", "test"))
.uri("lb://service-user")
.order(0)
.id("test-RequestTimeFilter")
).build();
}
}
13.2 自定义GlobalFilter
GlobalFilter:全局过滤器,不需要在配置文件中配置,作用在所有的路由上,最终通过GatewayFilterAdapter包装成GatewayFilterChain可识别的过滤器,它为请求业务以及路由的URI转换为真实业务服务的请求地址的核心过滤器,不需要配置,系统初始化时加载,并作用在每个路由上。
@Component
public class GlobleLoginFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 获取请求对象
ServerHttpRequest request = exchange.getRequest();
// 获取请求头
List<String> tokens = request.getHeaders().get("token");
if (tokens == null || tokens.isEmpty()){
// 获取响应对象
ServerHttpResponse response = exchange.getResponse();
// 构建错误结果
HashMap<String,Object> data = new HashMap<>();
data.put("code",401);
data.put("message","未登录");
DataBuffer buffer = null;
try {
byte[] bytes = "去登录".getBytes("utf-8");
buffer = response.bufferFactory().wrap(bytes);
// 设置完成相应,不会继续执行后面的filter
// response.setComplete();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().add("Content-Type","application/json;charset=UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
// 把结果写给客户端
return response.writeWith(Mono.just(buffer));
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
注意:要加上@Component注解,交给spring管理
13.3 Gateway跨域配置
spring:
cloud:
globalcors: #跨域配置
cors-configurations:
'[/**]':
allowedOrigins: "https://docs.spring.io" #允许的站点
allowedMethods: #允许的请求方式
- GET
- POST
- DELETE
- PUT
- HEAD
- CONNECT
- TRACE
- OPTIONS
allowHeaders: #允许的请求头
- Content-Type
13.4 Gateway超时
spring:
cloud:
gateway:
httpclient:
connect-timeout: 1000
response-timeout: 5s