SpringCloud学习笔记
SpringCloud
在微服务中,不同的服务板块是分开的,有自己的数据库。但是在业务中可能存在服务板块中互相调用的情况,比如订单服务中需要获取用户信息,这时候不能再自己的板块中直接进行查询,否则违反了微服务的理念,且如果数据库分开的话也无法实现查询,所以采取向其他服务发送请求的方式获取数据,在其他服务中调用相关的方法。要调用就需要在后端手动发送http请求。
如何在Java中发送http请求?
——利用RestTemplate,首先在启动类中利用bean注解注入对象,然后调用restTemplate的getForObject方法发送Get请求(其余类型的请求同理),参数为url,返回值类型为JSON。
一个服务既可以是提供者也可以是消费者
Eureka注册中心
Eureka出现要解决的问题
一个服务向另一个服务发送请求的时候需要url地址,但是url地址在不同的环境下比如生产环境、测试环境等是会变动的,且随着业务开发也可能会发生变更。如果在代码中写死后续就会难以维护。因此需要一个能够动态获取信息的方法。
Eureka注册中心在服务启动后,就会获取服务的信息,并保存下来。当消费者需要信息去访问提供者的时候就可以直接从Eureka中获取信息。
而提供者可能有多个,获取哪一个采用负载均衡算法。并且当服务在Eureka中注册后就会每隔30s向Eureka发送心跳,Eureka以此来确定服务是否存活,如果不存活信息就会被剔除。
记录服务信息心跳监控的是EurekaServer,服务提供者和消费者是EurekaClient。
Eureka实践
1.搭建注册中心
需要创建一个独立的微服务。
依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
然后编写启动类,其需要添加@EnableEurekaServer注解
并添加application.yml文件:
server:
port: 10086 #服务端口
spring:
application:
name: eurekaserver #eureka的服务名称
eureka:
client:
service-url: #eureka的地址信息
defaultZone: http://127.0.0.1:10086/eureka
注意:Eureka会将自己也注册,所以默认会有一个实例。
2.服务注册
第一步:引入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
第二步:application.yml文件编写配置:
spring:
application:
name: userservice
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
注意:需要作为EurekaClient的服务都需要注册。
测试需要启动两个实例,可以右键实例利用IDEA中的CopyConfiguration进行复制,然后修改端口避免冲突(编辑配置,在VM options中写入 -Dserver.port=8082)。启动后就可以在Eureka页面中看到一个服务的两个实例。
3.一个服务在Eureka中完成另外服务的拉取。
第一步:修改OrderService的代码,修改访问的url路径,用服务名代替ip、端口:
String url = "http://userservice/user/" + order.getUserId();
第二步:在order-service项目的启动类OrderApplication中的RestTemplate添加负载均衡注解:
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
@LoadBalanced就是负载均衡的注解。
Ribbon负载均衡
在后端发送服务名替代ip的请求时,浏览器是无法识别的,需要有人对这个请求进行处理,也就是利用这个服务名取Eureka中获取服务的地址,并且还要实现负载均衡以最终选取一个地址使用。这个功能的实现者就是Ribbon。
当服务发送请求的时候,会被负载均衡拦截器LoadBalancerInterceptor拦截,拦截后交给RibbonLoadBalancerClient处理,它会获取其中的服务器名称,交给动态服务列表负载均衡器DynamicServerListLoadBalancer,其根据名称向eureka-server中获取到一个服务器列表,再根据IRule接口中的负载均衡的方法如轮询、随机等规则获取一个实例并返回给RibbonLoadBalancerClient,再进行地址的替换以得到真实地址。
IRule接口中的负载均衡策略实现
内置负载均衡规则类 | 规则描述 |
---|---|
RoundRobinRule | 简单轮询服务列表来选择服务器。 |
AvailabilityFilteringRule | (1)在默认情况下,这台服务器如果3次连接失败,就会被设置为“短路”状态。短路状态将持续30s,如果再次连接失败,短路的持续时间会几何式增加。 (2)如果一个服务器的并发连接数过高,配置了AvailabilityFilteringRule规则的客户端也会将其忽略。 |
WeightedResponseTimeRule | 为每一个服务器赋予一个权重。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,并根据权重值影响选择。 |
ZoneAvoidanceRule | 以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。 |
BestAvailableRule | 忽略那些短路的服务器,并选择并发数较低的服务器。 |
RandomRule | 随机选择一个可用的服务器。 |
RetryRule | 重试机制的选择逻辑。 |
默认为ZoneAvoidanceRule,以区域可用的服务器为基础进行轮询。
如何修改负载均衡策略?
有两种方式:
1.代码方式:在order-service中的OrderApplication类中,定义一个新的IRule:
@Bean
public IRule randomRule(){
return new RandomRule();
}
在启动类中使用@Bean注解注入一个IRule实例,比如简单地返回一个RandomRule,这样就会改编为随机策略。这是全局配置,即调用所有其他的服务都会应用随机策略。
2.配置文件方式:在order-service的application.yml文件中,添加新的配置:
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #负载均衡规则
这样配置就会指定某个微服务应用随机策略,这里指定的是userservice。
Ribbon的懒加载和饥饿加载
Ribbon默认的是懒加载,即第一次访问时才会创建LeadBalanceClient,请求时间会很长。而饥饿加载则是在项目启动的时候就进行创建,降低第一次访问的耗时(在加载之后会默认放入缓存,只有第一次会很慢)。
开启饥饿加载的方法——配置:
ribbon:
eager-load:
enabled: true #开启饥饿加载
clients: userservice #指定对userservice这个服务饥饿加载
如果要对多个服务配置则:
ribbon:
eager-load:
enabled: true #开启饥饿加载
clients:
- userservice
- xxxservice
Nacos注册中心
安装
Nacos是阿里巴巴的产品,由于其更加完善和丰富的功能,现已成为SpringCloud的一个组件。
安装步骤:
1.下载安装包(推荐1.x版本)
2.解压
3.配置:配置conf文件夹里的application.conf,默认端口是8848。
4.启动:Windows命令:startup.cmd -m standalone
访问显示的地址,默认账号和密码都是nacos。
服务注册到Nacos
1.在cloud-demo父工程中添加spring-cloud-alibaba的管理依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
2.注释掉order-service和user-service中原有的eureka依赖。
3.添加nacos的客户端依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
4.修改user-service&order-service中的application.yml文件,注释eureka地址,添加nacos地址。
spring:
cloud:
nacos:
server-addr: localhost8848
5.启动并测试。
Nacos服务分级存储模型
服务-集群-实例
服务跨集群调用问题:服务调用尽可能选择本地集群的服务。
服务集群属性配置
1.修改application.yml,添加如下内容:
spring:
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: SC #配置集群名称,即机房位置,如SC(四川)
2.在Nacos控制台可以看到集群变化。
优先选择本地集群
在消费者服务中也进行配置,将二者放入同一个集群:
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: SC
要实现优先选择本地集群,需要修改IRule负载均衡策略为NacosRule:
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule
这种策略下会优先选择本地集群基础上采用随机策略。
Nacos权重负载均衡
根据服务器的性能差异进行权重分配
1.在Nacos控制台可以设置实例的权重值,首先选中实例后面的编辑按钮。
2.将权重设置为0.1,测试可以发现被访问到的频率会大大降低。
注:一般设置为0-1之间,权重越高访问频率越高,为0则不会被访问。
Nacos环境隔离——namespace
基于不同的环境(如开发环境、测试环境等)进行隔离。
Nacos会有一个默认的命名空间为public。
在Nacos可以手动创建命名空间,修改命名空间需要再代码中进行。
spring:
datasource:
url: jdbc:mysql://localhost:3306/test?useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.jsbc.Driver
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: SC
namespace: 792a7d5d-237b-46a0-a99a-fa8e98e4b0f9 #此处为nacos页面中的命名空间id而不是名称。
注:不同namespace下的服务是不可见的。
Eureka和Nacos的区别
服务消费者拉取信息后会将信息存储到服务列表缓存中,每隔30s重新拉取进行更新。
但是在Nacos中,消费者和提供者的信息拉取和健康监测有所不同。在Nacos中的实例分为临时实例和非临时实例,对于临时实例的提供者来说,和Eureka一样会有一个心跳检测机制,即每隔30s由提供者向Nacos发送信息,如果逾期未发信息就会被Nacos剔除,而非临时实例则是由Nacos主动向提供者发送信息,如果没有得到回复,不会剔除信息,而是会等待实例恢复健康。
对于消费者的信息拉取除了和Eureka一样的由消费者每隔30s主动拉取信息外,如果Nacos收到了来自提供者的不健康的信息,也会主动推送信息给消费者,消费者就能及时更新自己的服务列表缓存。
Nacos设置临时实例和非临时实例
spring:
cloud:
nacos:
discovery:
ephemeral: false #设置为非临时实例
Nacos配置管理
解决问题:实现若干微服务的统一配置管理,并且不用重启就能生效,实现热更新。
在Nacos页面选择配置管理,点击“+”手动新建配置,其中DataID一般为服务名称+运行环境+后缀名,如userservice-dev.yaml,配置内容是需要进行热更新的配置,而不是直接把application.yml中的内容直接拷贝。比如pattern: dateformat: yyyy-MM-dd HH:mm:ss。
在项目启动后需要先读取nacos中的配置文件,再读取本地配置文件application.yml,但是nacos配置文件地址又在application.yml文件中,因此需要一个优先级比二者都高的文件来装载nacos地址等相关信息,这个文件就是bootstrap.yml。
统一配置管理的相关设置:
1.引入Nacos的配置管理客户端依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
2.在userservice中的resource目录添加一个bootstrap.yml文件,这个文件是引导文件,优先级高于application.yml:
spring:
application:
name: userservice #微服务名称
profiles:
active: dev #开发环境,这里是dev
cloud:
nacos:
server-addr: localhost:8848
config:
file-extension: yaml #文件后缀名
bootstrap.yml文件内容实际上就是nacos配置文件地址+配置文件的ID信息(服务名称+开发环境+文件后缀名)
假设配置的是日期格式pattern,那么可以通过以下方式进行测试:
@RestController
@RequestMapping("/user")
public class UserController{
//注入nacos中的配置属性
@Value("${pattern.dateformat}")
private String dateformat;
//编写controller,通过日期格式化器来格式化现在时间并返回
@GetMapping("now")
public String now(){
return LocalDate.now().format(
DateTimeFormatter.ofPattern(dateformat,Locale.CHINA)
);
}
// ...
}
Nacos配置热更新
Nacos中的配置文件更新后,微服务无需重启就可以感知,但是要进行配置。有两种配置方法:
方式一:在@Value注入的变量所在的类上添加@RefreshScope注解
@SLf4j
@RestController
@RequestMapping("/user")
@RefreshScope
public class UserController{
@Value("${pattern.dateformat}")
private String dateformat;
}
方式二:使用@ConfigurationProperties注解(推荐)
@Component
@Data
@ConfigurationProperties(prefix="pattern")
public class PatternProperties{
private String dateformat;
}
然后自动注入配置类,并且使用get方法获取对象作为参数填入。
@RequestMapping("/user")
public class UserController{
@Autowired
private UserService userService;
@Autowired
private PatternProperties properties;
@GetMapping("now")
public String now(){
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(properties.getDateFormat))
}
}
Nacos集群搭建
搭建集群的基本步骤:
- 搭建数据库,初始化数据库表结构
- 下载nacos安装包
- 配置nacos
- 启动nacos集群
- nginx反向代理
集群Nacos配置
进入nacos的conf目录,找到cluster.conf.example文件,添加ip地址和端口号
如:
127.0.0.1:8845
127.0.0.1:8846
127.0.0.1:8847
配置mysql信息,修改application.properties文件
spring.datasource.platform=mysql
db.mum=1
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=123456
复制三份文件(这里是伪集群,因为在同一台服务器上配置),然后修改三个文件夹中的application.properties文件中的端口号
nacos1:
server.port=8845
nacos2:
server.port=8846
nacos3:
server.port=8847
然后分别启动三个nacos节点(不用加-m,默认是集群启动):startup.cmd
nginx反向代理,修改conf/nginx.conf文件
upstream nacos-cluster {
server 127.0.0.1:8845;
server 127.0.0.1:8846;
server 127.0.0.1:8847;
}
server {
listen 80;
server_name localhost;
location /nacos {
proxy_pass http://nacos-cluster;
}
}
然后Java代码中的application.yml文件中nacos端口把8848改为80。
http客户端Feign
RestTemplate方式调用会存在问题,代码可读性差,且如果URL很复杂,使用RestTemplate就会显得不够优雅。
因此可以使用Feign,Feign是一个声明式的http客户端,帮助我们优雅地发送请求。
使用Feign的步骤如下:
1.引入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.在order-service的启动类添加注解开启Feign的功能:
@EnableFeignClients
@SpringBootApplication
public class OrderApplication{
public static void main(String[] args){
SpringApplication.run(OrderApplication.class, args);
}
3.编写Feign客户端:
@FeignClient("userservice")
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
使用注解将发送请求所需要的信息进行声明,之后调用这个方法就可以实现发送请求了。
public class userService{
@Autowired
private OrderMapper orderMapper;
@Autowired
private UserClient userClient;
public Order queryOrderById(Long orderId){
//1.查询订单
Order order = orderMapper.findById(orderId);
//2.用Feign远程调用
User user = userClient.findById(order.getUserId());
//3.封装user到Order
order.setUser(user);
//4.返回
return order;
}
}
注:feign内部集成了Ribbon,实现了负载均衡。
总结Feign的使用步骤:1.引入依赖;2.添加@EnableFeignClients注解;3.编写FeignClient接口;4.使用FeignClient中定义的方法代替RestTemplate。
自定义Feign配置
类型 | 作用 | 说明 |
---|---|---|
feign.Logger.Level | 修改日志级别 | 包含四种不同的级别:NONE、BASIC、HEADERS、FULL |
feign.codec.Decoder | 响应结果的解析器 | http远程调用的结果做解析,例如解析JSON字符串为Java对象 |
feign.codec.Encoder | 请求参数编码 | 将请求参数编码,便于通过http请求发送 |
feign.Contract | 支持的注解格式 | 默认是SpringMVC的注解 |
feign.Retryer | 失败重试机制 | 请求失败的重试机制,默认是没有,不过会使用Riibon的重试 |
自定义配置会覆盖默认配置
配置有两种方式,第一种是基于配置文件,第二种是基于Java代码
方式一:配置文件方式
1.全局生效
feign:
client:
config:
default: #这里用default就是全局配置,如果是写服务名称某个微服务的配置
loggerLevel: FULL #日志级别
2.局部生效:
feign:
client:
config:
userservice:
loggerLevel: FULL
方式二:Java代码方式,需要先声明一个Bean:
public class FeignClientConfiguration {
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.BASIC;
}
}
1.如果是全局配置,则把它放到@EnableFeignClients这个注解中:
@EnableFeignClients(defaultConfiguration = FeignClientConfiguration.class)
2.如果是局部配置,则放到@FeignClient注解中:
@FeignClient(value = "userservice", configuration = FeignClientConfiguration.class)
Feign性能优化
Feign底层的客户端实现由URLConnection(默认实现,不支持连接池)、ApacheHttpClient、OKHttp。
Feign性能优化:
1.使用连接池代替默认的URLConnection。
2.日志级别,最好用basic或者none。
Feign添加HttpClient的支持:
引入依赖:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
配置连接池:
feign:
client:
config:
default:
loogerLevel: BASIC #日志级别,BASIC就是基本的请求和响应信息
httpclient:
enabled: true #开启feign对HttpClient的支持
max-connections: 200 #最大的连接数
max-connections-per-route: 50 #每个路径的最大连接数
Feign的最佳实践
方式一(继承):给消费者的FeignClient和提供者的controller定义统一的父接口作为标准。
public interface UserAPI{
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
@FeignClient(value = "userservice")
public interface UserClient extends UserAPI{}
@RestController
public class UserController implements UserAPI{
}
问题:会造成紧耦合,而且父接口参数列表中的映射不会被继承。
方式二(抽取):将FeignClient抽取为独立模块,并发接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有的消费者使用。
步骤如下:
1.创建一个module,命名为feign-api,然后引入feign的starter依赖。
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
2.将order-service中编写的UserClient、User、DefaultFeignConfiguration都复制到feign-api项目中。
3.在order-service中引入feign-api的依赖,将原有的复制过去的部分删除。
4.修改order-service中的所有与上述三个组件有关的import部分,改成导入feign-api中的包。
<dependency>
<groupId>cn.itcast.demo</groupId>
<artifactId>feign-api</artifactId>
<version>1.0</version>
</dependency>
5.重启测试。
当定义的FeignClient不在SpringBootApplication的扫描包范围时,这些FeignClient无法使用。
有两种方式解决:
方式一:制定FeignClient所在的包:
@EnableFeignClients(basePackages = "cn.itcast.feign.clients")
方式二:制定FeignClient字节码(更精准):
@EnableFeignCliengts(clients = {UserClient.class})
Gateway统一网关
网关解决的问题:
- 身份认证和权限校验,避免敏感服务被任意读取。
- 服务路由,负载均衡。
- 请求限流。
在SpringCloud中网关的实现包括两种:gateway和zuul。其中zuul是基于Servlet的实现,属于阻塞式编程,而gateway是基于Spring5中提供的WebFlux,属于响应式编程实现,具备更好的性能。
搭建网关服务
1.创建新的module,引入SpringCloudGateway的依赖和nacos的服务发现依赖:
<!--网关依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos服务发现依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
创建启动类
@SpringBootApplication
public class GatewayApplication{
public static void main(Stringp[] args){
SpringApplication.run(GatewayApplication.class, args);
}
}
2.编写路由配置及nacos地址:
server:
port: 10010 #网关端口
spring:
application:
name: gateway #服务名称
cloud:
nacos:
server-addr: localhost:8848
gateway:
routes: #网关路由配置
- id: user-service #路由id,自定义,唯一即可
# uri: http://127.0.0.1:8081 #路由目标地址,http就是固定地址
uri: lb://userservice #路由的目标地址,lb就是负载均衡,后面跟服务名称
predicates: #路由断言,也就是判断请求是否符合路由规则的条件
- Path=/user/** #这个是按照路径匹配,只要一/user/开头就符合要求
网关路由可以配置的内容包括:
- 路由id:路由唯一标识
- uri: 路由目的地,支持lb和http两种
- predicates:路由断言,判断请求是否符合要求,符合则转发到路由目的地
- filters:路由过滤器,处理请求或响应
路由断言工厂
在配置文件中写的断言规则字符串会被Predicate Factory读取并处理,转变为路由判断的条件。
Spring提供了11种基本的断言工厂:
名称 | 说明 | 示例 |
---|---|---|
After | 是某个时间点后的请求 | - After=2037-01-20T17:42:47.789-07:00[America/Denver] |
Before | 是某个时间点之前的请求 | - Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai] |
Between | 是某两个时间点之间的请求 | - Between=2037-01-20T17:42:47.789-07:00[America/Denver],2037-01-21T17:42:47.789-07:00[America/Denver] |
Cookie | 请求必须包含某些cookie | - Cookie=chocolate,ch.p |
Header | 请求必须包含某些header | - Header=X-Request-Id,\d+ |
Host | 请求必须是访问某个host(域名) | - Host=**.somehost.org, **.anotherhost.org |
Method | 请求方式必须是指定方式 | - Method=GET,POST |
Path | 请求路径必须符合指定规则 | - Path=/red/{segment},/blue/** |
Query | 请求参数必须包含指定参数 | - Query=name,Jack或者 - Query=name |
RemoteAddr | 请求者的ip必须是指定范围 | - RemoteAddr=192.168.1.1/24 |
Weight | 权重处理 |
例如使用After:
predicates:
- Oath=/order/**
- After=2031-04-13T15:14:47.433+08:00[Asia/Shanghai]
时间不在规定范围之后就会404。
路由过滤器GatewayFilter
可以对进入网关的请求和微服务返回的响应做处理。
Spring提供了31种不同的路由器过滤工厂,例如:
名称 | 说明 |
---|---|
AddRequestHeader | 给当前请求添加一个请求头 |
RemoveRequestHeader | 移出请求中的一个请求头 |
AddResponseHeader | 给响应结果中添加一个响应头 |
RemoveResponseHeader | 从响应结果中移除一个响应头 |
RequestRateLimiter | 限制请求的流量 |
… |
例如:给userservice的路由添加过滤器,所有进入userservice的请求添加一个请求头。
spring:
cloud:
gateway:
route: #网关路由配置
- id: user-service
uri: lb://userservice
predicates:
- Path=/user/**
filters: #过滤器
- AddRequestHeader=Truth, Itcast is freaking awosome! #添加请求头
@GetMapping("/{id}")
public User quertById(@PathVariable("id") Long id,
@RequestHeader(value = "Truth", required = false) String truth){
System.out.println("truth:" + truth);
return userService.queryById(id);
}
如果要对所有的路由都生效,需要将路由过滤器配置到default-filters下,在任意一个服务中配置都可以:
spring:
application:
name: gateway
cloud:
nacos:
server-addr: localhost:8848
gateway:
routes:
- id: user-service
uri: lb://userservice
predicates:
- Path=/user/**
- id: order-service
uri: ln://orderservice
predicates:
- Path=/order/**
default-filters:
- AddRequestHeader=Truth, Itcast is freaming awesome!
全局过滤器GlobalFilter
前面的过滤器是通过配置定义的,是spring写死的,而要实现自定义配置,就可以通过GlobalFilter。定义方式就是实现GlobalFIlter接口。
public ingterface GlobalFilter{
/**
* 处理当前请求,有必要的话通过{@Link GatewayFilterChain}将请求交给下一个过滤器处理
* @Param exchange 请求上下文,里面可以获取Request、Response等信息
* @Param chain 用来把请求委托给下一个过滤器
* @return {@code Mono<Void>} 返回标识当前过滤器业务结束
*/
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
案例:定义全局过滤器,拦截并判断用户身份。
需求:定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件:
- 参数中是否有authorization
- authorization参数值是否为admin
如果同时满足则放行,否则拦截。
实现步骤:1.获取请求参数 2.获取参数中的authorization参数 3.判断参数值是否等于admin 4.如果是则放行,否则拦截。
@Order(-1)
@Component
public class AuthorizeFilter implements GlobalFilter{
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain){
//1.获取请求参数
ServerHttpRequest request = exchange.getRequest();
MultiValueMap<String,String> params = request.getQueryParams();
//2.获取参数中的authorization参数
String auth = params.getFirst("authorization");
//3.判断参数值是否等于admin
if ("admin".equals(auth)){
//4.是,放行
return chain.filter(exchange);
}
//5.否,拦截
//5.1.设置状态码
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
//5.2.拦截请求
return exchange.getResponse()/setComplete();
}
}
注:@Order()里面的值是int值的取值范围,可以为负数,越小优先级越高。
过滤器执行顺序
请求进入网关后会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter。
请求路由后,会将这三种过滤器合并到一个过滤器链(集合)中,排序后依次执行每个过滤器。
- 每个过滤器都必须指定一个int类型的order值,值越小优先级越高,执行顺序越靠前。
- GlobalFilter通过实现Ordered接口或者添加@Order注解来指定order值,由程序员指定。
- 路由过滤器和defaultFilter的order由spring指定,默认是按照声明顺序从1递增。
- 当过滤器的order值一样时,会按照defaultFilter > 路由过滤器 > GlobalFilter的顺序执行。
跨域问题处理
跨域:域名不一致就是跨域,包括:域名不同,域名相同但端口不同。
跨域问题:浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题。
解决方案:CORS
配置:
spring:
cloud:
gateway:
# ...
globalcors: #全局的跨域处理
add-to-simple-url-handler-mapping: true #解决options请求被拦截问题
corsConfigurations:
'[/**]':
allowedOrigins: #允许哪些网站的跨域请求
- "http://localhost:8090"
- "http://www.leyou.com"
allowedMethods: #允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" #允许在请求中携带的头信息
allowCredentials: true #是否允许携带cookie
maxAge: 360000 #这次跨域检测的有效期
}
}
注:@Order()里面的值是int值的取值范围,可以为负数,越小优先级越高。
过滤器执行顺序
请求进入网关后会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter。
请求路由后,会将这三种过滤器合并到一个过滤器链(集合)中,排序后依次执行每个过滤器。
- 每个过滤器都必须指定一个int类型的order值,值越小优先级越高,执行顺序越靠前。
- GlobalFilter通过实现Ordered接口或者添加@Order注解来指定order值,由程序员指定。
- 路由过滤器和defaultFilter的order由spring指定,默认是按照声明顺序从1递增。
- 当过滤器的order值一样时,会按照defaultFilter > 路由过滤器 > GlobalFilter的顺序执行。
跨域问题处理
跨域:域名不一致就是跨域,包括:域名不同,域名相同但端口不同。
跨域问题:浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题。
解决方案:CORS
配置:
spring:
cloud:
gateway:
# ...
globalcors: #全局的跨域处理
add-to-simple-url-handler-mapping: true #解决options请求被拦截问题
corsConfigurations:
'[/**]':
allowedOrigins: #允许哪些网站的跨域请求
- "http://localhost:8090"
- "http://www.leyou.com"
allowedMethods: #允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" #允许在请求中携带的头信息
allowCredentials: true #是否允许携带cookie
maxAge: 360000 #这次跨域检测的有效期