?導(dǎo)航:
【黑馬Java筆記+踩坑匯總】JavaSE+JavaWeb+SSM+SpringBoot+瑞吉外賣+SpringCloud/SpringCloudAlibaba+黑馬旅游+谷粒商城
目錄
1.Nacos配置管理
1.1.統(tǒng)一配置管理
1.1.1.在Nacos中添加配置文件
1.1.2.從微服務(wù)拉取配置,@Value("${xxx}")
1.2.配置熱更新
1.2.1.方式一(推薦),類注解@RefreshScope
1.2.2.方式二,@ConfigurationProperties,@Component
1.3.配置共享
1.3.1.創(chuàng)建共享配置和讀取
1.3.2.配置共享的優(yōu)先級
1.4.搭建Nacos集群
1.5.其他
1.5.1.命名空間
1.5.2.根據(jù)服務(wù)創(chuàng)建命名空間,各命名空間根據(jù)環(huán)境分組
1.5.3.按類型抽取配置,加載多配置集?
2.Feign遠程調(diào)用
2.0.RestTemplate存在問題
2.1.Feign替代RestTemplate
2.1.1.引入依賴
2.1.2.開啟feign,添加注解@EnableFeignClients
2.1.3.編寫Feign的客戶端,@FeignClient
2.1.4.遠程調(diào)用
2.2.自定義配置
2.2.1.配置文件方式自定義配置
2.2.2.Java代碼方式自定義配置
2.3.Feign性能優(yōu)化,HttpClient
2.4.最佳實踐
2.4.1.繼承方式(高耦合不推薦)
2.4.2.抽取方式(推薦)
3.Gateway服務(wù)網(wǎng)關(guān)
3.1.網(wǎng)關(guān)概述
3.2.gateway快速入門
3.2.1.創(chuàng)建gateway服務(wù),引入依賴
3.2.2.編寫啟動類
3.2.3.編寫基礎(chǔ)配置和路由規(guī)則
3.2.4.重啟測試
5)網(wǎng)關(guān)路由的流程圖
3.3.斷言工廠
3.4.網(wǎng)關(guān)過濾器工廠
3.4.1.路由過濾器的種類
3.4.2.請求頭過濾器,AddRequestHeader
3.4.3.默認過濾器,default-filters
3.4.4.總結(jié)
3.5.全局過濾器,GlobalFilter
3.5.1.全局過濾器作用
3.5.2.自定義全局過濾器,過濾未登錄
3.5.3.過濾器執(zhí)行順序
3.6.跨域問題
3.6.1.什么是跨域問題
3.6.2.模擬跨域問題
3.6.3.解決跨域問題,CORS
1.Nacos配置管理
Nacos除了可以做注冊中心,同樣可以做配置管理來使用。
1.1.統(tǒng)一配置管理
當微服務(wù)部署的實例越來越多,達到數(shù)十、數(shù)百時,逐個修改微服務(wù)配置就會讓人抓狂,而且很容易出錯。我們需要一種統(tǒng)一配置管理方案,可以集中管理所有實例的配置。
Nacos一方面可以將配置集中管理,另一方面可以在配置變更時,及時通知微服務(wù),實現(xiàn)配置的熱更新。
1.1.1.在Nacos中添加配置文件
如何在nacos中管理配置呢?
然后在彈出的表單中,填寫配置信息:
這里分組名稱默認DEFAULT_GROUP,我們看服務(wù)列表也可以看到所有服務(wù)默認都在這個分組:
注意:
- 項目的核心配置,需要熱更新的配置才有放到nacos管理的必要。基本不會變更的一些配置還是保存在微服務(wù)本地比較好。
- 配置寫yaml,而不是yml。
1.1.2.從微服務(wù)拉取配置,@Value("${xxx}")
微服務(wù)要拉取nacos中管理的配置,并且與本地的application.yml配置合并,才能完成項目啟動。
問題:nacos配置要先于yml,但如果尚未讀取yml,又如何得知nacos地址并獲取nacos配置呢?
答案:因此spring引入了一種新的配置文件:bootstrap.yaml文件,優(yōu)先級高于application.yml,會在application.yml之前被讀取。流程如下:
bootstrap譯為引導(dǎo)
1)引入nacos-config依賴
首先,在user-service服務(wù)中,引入nacos-config的客戶端依賴:
<!--nacos配置管理依賴-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- 在SpringBoot 2.4.x的版本之后,對于bootstrap.properties/bootstrap.yaml配置文件(我們合起來成為Bootstrap配置文件)的支持,需要導(dǎo)入如下的依賴-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
<version>3.1.4</version>
</dependency>
2)添加bootstrap.yaml
然后,在user-service中添加一個bootstrap.yaml文件,需要配置服務(wù)名、開發(fā)環(huán)境、nacos地址、后綴名,對應(yīng)nacos添加的配置文件名。內(nèi)容如下:
spring:
application:
name: userservice # 服務(wù)名稱
profiles:
active: dev #開發(fā)環(huán)境,這里是dev
cloud:
nacos:
server-addr: localhost:8848 # Nacos地址
config:
file-extension: yaml # 文件后綴名
這里會根據(jù)spring.cloud.nacos.server-addr獲取nacos地址,再根據(jù)
${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
作為文件id,來讀取配置。
本例中,就是去讀取userservice-dev.yaml
:
2.5)刪除application.yml中的重復(fù)配置,包括服務(wù)名稱、nacos地址、開發(fā)環(huán)境
3)讀取nacos配置,@Value
在user-service中的UserController中添加業(yè)務(wù)邏輯,讀取pattern.dateformat配置:
完整代碼:
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Value("${pattern.dateformat}")
private String dateformat;
@GetMapping("now")
public String now(){
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
}
// ...略
}
在頁面訪問,可以看到效果:
1.2.配置熱更新
我們最終的目的,是修改nacos中的配置后,微服務(wù)中無需重啟即可讓配置生效,也就是配置熱更新。
要實現(xiàn)配置熱更新,可以使用兩種方式:
1.2.1.方式一(推薦),類注解@RefreshScope
在@Value注入的變量所在類上添加注解@RefreshScope:
1.2.2.方式二,@ConfigurationProperties,@Component
在user-service服務(wù)中,添加一個類,讀取patterrn.dateformat屬性:
@Component
@Data
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {
private String dateformat;
}
在UserController中使用這個類代替@Value:
完整代碼:
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private PatternProperties patternProperties;
@GetMapping("now")
public String now(){
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(patternProperties.getDateformat()));
}
// 略
}
1.3.配置共享
1.3.1.創(chuàng)建共享配置和讀取
其實微服務(wù)啟動時,會去nacos讀取多個配置文件,例如:
-
[spring.application.name]-[spring.profiles.active].yaml
,例如:userservice-dev.yaml [spring.application.name].yaml
,例如:userservice.yaml
而[spring.application.name].yaml
不包含環(huán)境,因此可以被多個環(huán)境共享。
下面我們通過案例來測試配置共享
1)添加一個環(huán)境共享配置
我們在nacos中添加一個userservice.yaml文件:
2)在user-service中讀取共享配置
在user-service服務(wù)中,修改PatternProperties類,讀取新添加的屬性:
在user-service服務(wù)中,修改UserController,添加一個方法:
3)測試。運行兩個UserApplication,使用不同的profile
修改UserApplication2這個啟動項,改變其profile值:
這樣,UserApplication(8081)使用的profile是dev,UserApplication2(8082)使用的profile是test。
啟動UserApplication和UserApplication2
訪問dev環(huán)境的http://localhost:8081/user/prop,結(jié)果:共享配置和dev環(huán)境的配置都可以讀到
訪問test環(huán)境的http://localhost:8082/user/prop,結(jié)果:讀不到dev環(huán)境配置,能讀到共享配置
可以看出來,不管是dev,還是test環(huán)境,都讀取到了envSharedValue這個屬性的值。
1.3.2.配置共享的優(yōu)先級
當nacos、服務(wù)本地同時出現(xiàn)相同屬性時,優(yōu)先級有高低之分:
nacos的環(huán)境>nacos的共享>本地
1.4.搭建Nacos集群
Nacos生產(chǎn)環(huán)境下一定要部署為集群狀態(tài)
搭建集群的基本步驟:
-
搭建數(shù)據(jù)庫,初始化數(shù)據(jù)庫表結(jié)構(gòu)
-
下載nacos安裝包
-
配置nacos
進入nacos的conf目錄,修改配置文件cluster.conf.example,重命名為cluster.conf:
127.0.0.1:8845
127.0.0.1.8846
127.0.0.1.8847
修改application.properties文件
spring.datasource.platform=mysql
db.num=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=123
-
啟動nacos集群
將nacos文件夾復(fù)制三份,分別命名為:nacos1、nacos2、nacos3
然后分別修改三個文件夾中的application.properties,
nacos1:
server.port=8845
nacos2:
server.port=8846
nacos3:
server.port=8847
然后分別啟動三個nacos節(jié)點:
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;
}
}
而后在瀏覽器訪問:http://localhost/nacos即可。
代碼中application.yml文件配置如下:
spring:
cloud:
nacos:
server-addr: localhost:80 # Nacos地址
1.5.其他
1.5.1.命名空間
命名空間用于將開發(fā)測試生產(chǎn)三種環(huán)境、或者各微服務(wù)之間配置隔離。默認命名空間是public:
不同命名空間設(shè)置不同配置:?
指定命名空間:?
?
1.5.2.根據(jù)服務(wù)創(chuàng)建命名空間,各命名空間根據(jù)環(huán)境分組
1.5.3.按類型抽取配置,加載多配置集?
開發(fā)時為了方便可以將配置文件寫在項目中,等發(fā)布后再抽取到nacos中。?
將datasource相關(guān)配置抽取成一個配置,在coupon命名空間下,分組名為dev(根據(jù)環(huán)境分組) :
使用:
或者bootstrap.yml?
spring:
application:
name: gulimall-coupon
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
file-extension: yaml
namespace: xxxx
extension-configs:
- data-id: datasource.yml
group: dev
refresh: true
2.Feign遠程調(diào)用
2.0.RestTemplate存在問題
先來看我們以前利用RestTemplate發(fā)起遠程調(diào)用的代碼:
存在問題:
?代碼可讀性差,編程體驗不統(tǒng)一
?參數(shù)復(fù)雜URL難以維護
Feign是一個聲明式的http客戶端,官方地址:https://github.com/OpenFeign/feign
其作用就是幫助我們優(yōu)雅的實現(xiàn)http請求的發(fā)送,解決上面提到的問題。
2.1.Feign替代RestTemplate
Fegin的使用步驟如下:
2.1.1.引入依賴
我們在order-service服務(wù)的pom文件中引入feign的依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.1.2.開啟feign,添加注解@EnableFeignClients
在order-service的啟動類添加注解開啟Feign的功能:
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
@EnableFeignClients
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
// @Bean
// @LoadBalanced
// public RestTemplate restTemplate(){
// return new RestTemplate();
// }
}
2.1.3.編寫Feign的客戶端,@FeignClient
在order-service中新建一個接口,內(nèi)容如下:
package cn.itcast.order.clients;
@FeignClient("userservice")
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
聲明遠程調(diào)用的需要的信息,比如:
- 服務(wù)名稱:userservice
- 請求方式:GET
- 請求路徑:/user/{id}
- 請求參數(shù):Long id
- 返回值類型:User
?
這樣,F(xiàn)eign就可以幫助我們發(fā)送http請求,無需自己使用RestTemplate來發(fā)送了。
2.1.4.遠程調(diào)用
修改order-service中的OrderService類中的queryOrderById方法,使用Feign客戶端代替RestTemplate:
是不是看起來優(yōu)雅多了。
總結(jié)
使用Feign的步驟:
① 引入依賴
② 添加@EnableFeignClients注解
③ 編寫FeignClient接口
④ 使用FeignClient中定義的方法代替RestTemplate
2.2.自定義配置
Feign可以支持很多的自定義配置,如下表所示:
類型 | 作用 | 說明 |
---|---|---|
feign.Logger.Level | 修改日志級別 | 包含四種不同的級別:NONE、BASIC、HEADERS、FULL |
feign.codec.Decoder | 響應(yīng)結(jié)果的解析器 | http遠程調(diào)用的結(jié)果做解析,例如解析json字符串為java對象 |
feign.codec.Encoder | 請求參數(shù)編碼 | 將請求參數(shù)編碼,便于通過http請求發(fā)送 |
feign. Contract | 支持的注解格式 | 默認是SpringMVC的注解 |
feign. Retryer | 失敗重試機制 | 請求失敗的重試機制,默認是沒有,不過會使用Ribbon的重試 |
一般情況下,默認值就能滿足我們使用,一般只需要配置日志級別為basic。如果要自定義時,只需要創(chuàng)建自定義的@Bean覆蓋默認Bean即可。
下面以日志為例來演示如何自定義配置。
2.2.1.配置文件方式自定義配置
基于配置文件修改feign的日志級別可以針對單個服務(wù):
feign:
client:
config:
userservice: # 針對某個微服務(wù)的配置
loggerLevel: FULL # 日志級別
也可以針對所有服務(wù):
feign:
client:
config:
default: # 這里用default就是全局配置,如果是寫服務(wù)名稱,則是針對某個微服務(wù)的配置
loggerLevel: FULL # 日志級別
日志的級別分為四種:
- NONE(默認):不記錄任何日志信息,這是默認值。
- BASIC:僅記錄請求的方法,URL以及響應(yīng)狀態(tài)碼和執(zhí)行時間
- HEADERS:在BASIC的基礎(chǔ)上,額外記錄了請求和響應(yīng)的頭信息
- FULL:記錄所有請求和響應(yīng)的明細,包括頭信息、請求體、元數(shù)據(jù)。
2.2.2.Java代碼方式自定義配置
也可以基于Java代碼來修改日志級別,先聲明一個類,然后聲明一個Logger.Level的對象:
public class DefaultFeignConfiguration {
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.BASIC; // 日志級別為BASIC
}
}
如果要全局生效,將其放到啟動類的@EnableFeignClients這個注解中:
@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration .class)
如果是局部生效,則把它放到對應(yīng)的@FeignClient這個注解中:
@FeignClient(value = "userservice", configuration = DefaultFeignConfiguration .class)
2.3.Feign性能優(yōu)化,HttpClient
Feign底層發(fā)起http請求,依賴于其它的框架。
Feign底層客戶端實現(xiàn)包括:
?URLConnection(默認,不推薦):默認實現(xiàn),不支持連接池,三次握手傷害性能
?Apache HttpClient :支持連接池
?OKHttp:支持連接池
因此提高Feign的性能主要手段就是使用連接池代替默認的URLConnection。
這里我們用Apache的HttpClient來演示。
1)引入依賴
在order-service的pom文件中引入Apache的HttpClient依賴:
<!--httpClient的依賴 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
2)配置連接池
在order-service的application.yml中添加配置:
feign:
client:
config:
default: # default全局的配置
loggerLevel: BASIC # 日志級別,BASIC就是基本的請求和響應(yīng)信息
httpclient:
enabled: true # 開啟feign對HttpClient的支持
max-connections: 200 # 最大的連接數(shù)
max-connections-per-route: 50 # 每個路徑的最大連接數(shù)
實際應(yīng)用中,需要不斷調(diào)試測出最合適的連接數(shù)。
接下來,在FeignClientFactoryBean中的loadBalance方法中打斷點:
Debug方式啟動order-service服務(wù),可以看到這里的client,底層就是Apache HttpClient:
總結(jié),F(xiàn)eign的優(yōu)化:
- 1.日志級別盡量用basic
- 2.使用HttpClient或OKHttp代替URLConnection
① 引入feign-httpClient依賴
② 配置文件開啟httpClient功能,設(shè)置連接池參數(shù)
2.4.最佳實踐
所謂最近實踐,就是使用過程中總結(jié)的經(jīng)驗,最好的一種使用方式。
觀察可以發(fā)現(xiàn),Feign的客戶端與服務(wù)提供者的controller代碼非常相似:
feign客戶端:
UserController:
有沒有一種辦法簡化這種重復(fù)的代碼編寫呢?
2.4.1.繼承方式(高耦合不推薦)
一樣的代碼可以通過繼承來共享:
1)定義一個API接口,利用定義方法,并基于SpringMVC注解做聲明。
2)Feign客戶端和Controller都集成改接口
優(yōu)點:
- 簡單
- 實現(xiàn)了代碼共享
缺點:
- 服務(wù)提供方、服務(wù)消費方緊耦合
- 參數(shù)列表中的注解映射并不會繼承,因此Controller中必須再次聲明方法、參數(shù)列表、注解
2.4.2.抽取方式(推薦)
將Feign的Client抽取為獨立模塊,并且把接口有關(guān)的POJO、默認的Feign配置都放到這個模塊中,提供給所有消費者使用。
例如,將order-service模塊的UserClient、User實體類、Feign的默認配置都抽取到一個feign-api包中,所有微服務(wù)引用該依賴包,即可直接使用。
抽取方式代碼實現(xiàn),包掃描問題
1)抽取
首先創(chuàng)建一個module,命名為feign-api:
項目結(jié)構(gòu):cn.itcast.feign
在feign-api中然后引入feign的starter依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
然后,order-service中編寫的UserClient、User、yml的DefaultFeignConfiguration都復(fù)制到feign-api項目中
2)在order-service中使用feign-api
首先,刪除order-service中的UserClient、User、DefaultFeignConfiguration等類或接口。
在order-service的pom文件中中引入feign-api的依賴:
<dependency>
<groupId>cn.itcast.demo</groupId>
<artifactId>feign-api</artifactId>
<version>1.0</version>
</dependency>
修改order-service中的所有與上述三個組件有關(guān)的導(dǎo)包部分,改成導(dǎo)入feign-api中的包
3)bean掃描問題導(dǎo)致測試報錯
重啟后,發(fā)現(xiàn)服務(wù)報錯了:
這是因為UserClient現(xiàn)在在cn.itcast.feign.clients包下,
而order-service的@EnableFeignClients注解是在cn.itcast.order包下,不在同一個包,無法掃描到UserClient。
4)解決掃描包問題
方式一:掃描整個包
指定Feign應(yīng)該掃描的包:
@EnableFeignClients(basePackages = "cn.itcast.feign.clients")
方式二(推薦):掃描指定clients
指定需要加載的Client接口:
@EnableFeignClients(clients = {UserClient.class})
3.Gateway服務(wù)網(wǎng)關(guān)
Spring Cloud Gateway 是 Spring Cloud 的一個全新項目,該項目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等響應(yīng)式編程和事件流技術(shù)開發(fā)的網(wǎng)關(guān),它旨在為微服務(wù)架構(gòu)提供一種簡單有效的統(tǒng)一的 API 路由管理方式。
3.1.網(wǎng)關(guān)概述
Gateway網(wǎng)關(guān)是我們服務(wù)的守門神,所有微服務(wù)的統(tǒng)一入口。
gateway譯為網(wǎng)關(guān),門口,通道?
網(wǎng)關(guān)的核心功能特性:
- 請求路由
- 權(quán)限控制
- 限流
架構(gòu)圖:
權(quán)限控制:網(wǎng)關(guān)作為微服務(wù)入口,需要校驗用戶是是否有請求資格,如果沒有則進行攔截。
路由和負載均衡:一切請求都必須先經(jīng)過gateway,但網(wǎng)關(guān)不處理業(yè)務(wù),而是根據(jù)某種規(guī)則,把請求轉(zhuǎn)發(fā)到某個微服務(wù),這個過程叫做路由。當然路由的目標服務(wù)有多個時,還需要做負載均衡。
限流:當請求流量過高時,在網(wǎng)關(guān)中按照下流的微服務(wù)能夠接受的速度來放行請求,避免服務(wù)壓力過大。
在SpringCloud中網(wǎng)關(guān)的實現(xiàn)包括兩種:
- zuul(阻塞式編程)
- gateway(推薦,響應(yīng)式編程)
Zuul是基于Servlet的實現(xiàn),屬于阻塞式編程。而SpringCloudGateway則是基于Spring5中提供的WebFlux,屬于響應(yīng)式編程的實現(xiàn),具備更好的性能。
擴展:阻塞式編程和響應(yīng)式編程
阻塞式編程和響應(yīng)式編程是兩種不同的編程范式,它們在處理異步操作時有著不同的理念和實現(xiàn)方式。
阻塞式編程:
同步阻塞模型: 在阻塞式編程中,程序執(zhí)行會被阻塞(暫停)直到某個操作完成。這意味著當一個線程執(zhí)行一個可能耗時的操作時,整個程序會被阻塞,直到該操作完成。
等待模型: 阻塞式編程通常采用等待模型,即程序等待外部資源(例如文件、網(wǎng)絡(luò)請求、數(shù)據(jù)庫查詢等)返回結(jié)果。在等待期間,線程處于阻塞狀態(tài),不能執(zhí)行其他任務(wù)。
響應(yīng)式編程:
異步非阻塞模型: 響應(yīng)式編程采用異步非阻塞的模型。異步意味著操作不會阻塞程序的執(zhí)行,而是在后臺執(zhí)行。非阻塞意味著在等待結(jié)果的同時,程序可以繼續(xù)執(zhí)行其他任務(wù)。
事件驅(qū)動: 響應(yīng)式編程通常是事件驅(qū)動的。程序通過訂閱事件或觀察者模式來響應(yīng)數(shù)據(jù)變化或操作完成的通知,而不是通過主動輪詢或等待。
多線程和異步的區(qū)別
- 多線程涉及在一個進程中創(chuàng)建多個線程,而異步編程涉及以非阻塞方式執(zhí)行任務(wù)。因此,異步可以是單線程異步(例如洗盤子,雖然我同時只能洗一個盤子,但我一個個洗完后把所有廢水統(tǒng)一倒掉,因此在單個任務(wù)中完成了多個子任務(wù),時間得到了優(yōu)化。),也可以是多線程異步。
- 多線程需要顯式地管理線程同步和通信,而異步編程可以使用回調(diào)或承諾(callbacks/promises)等編程結(jié)構(gòu)來處理異步操作。)
對比:
- 并發(fā)性:
- 阻塞式編程通常需要使用多線程來處理并發(fā),而響應(yīng)式編程通過異步處理來支持高并發(fā)。
- 資源利用率:
- 阻塞式編程可能會導(dǎo)致線程資源浪費,因為每個線程在等待時都不能執(zhí)行其他任務(wù)。響應(yīng)式編程通過異步非阻塞模型更有效地利用系統(tǒng)資源。
- 復(fù)雜性:
- 阻塞式編程可能導(dǎo)致代碼復(fù)雜性增加,因為需要處理線程同步和阻塞的問題。響應(yīng)式編程通常可以更簡潔地處理異步操作。
- 適用場景:
- 阻塞式編程適用于簡單的同步操作,而響應(yīng)式編程更適用于處理大量異步事件,例如網(wǎng)絡(luò)請求、實時數(shù)據(jù)流等。
3.2.gateway快速入門
網(wǎng)關(guān)的基本路由功能,基本步驟如下:
- 創(chuàng)建SpringBoot工程gateway,引入網(wǎng)關(guān)依賴
- 編寫啟動類
- 編寫基礎(chǔ)配置和路由規(guī)則
- 啟動網(wǎng)關(guān)服務(wù)進行測試
3.2.1.創(chuàng)建gateway服務(wù),引入依賴
創(chuàng)建服務(wù):
引入依賴:
<!--網(wǎng)關(guān)-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos服務(wù)發(fā)現(xiàn)依賴-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
父工程管理依賴<dependencyManagement>有:
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.2.6.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency>
所以子模塊getway引入spring-cloud-starter-alibaba-nacos-discovery不需要指定版本
3.2.2.編寫啟動類
cn.itcast.gateway包下?
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
3.2.3.編寫基礎(chǔ)配置和路由規(guī)則
創(chuàng)建application.yml文件,內(nèi)容如下:
server:
port: 10010 # 網(wǎng)關(guān)端口
spring:
application:
name: gateway # 服務(wù)名稱
cloud:
#網(wǎng)關(guān)需要被nacos注冊中心管理
nacos:
server-addr: localhost:8848 # nacos地址
gateway:
routes: # 網(wǎng)關(guān)路由配置
#第一組網(wǎng)關(guān)路由配置,針對于服務(wù)user-service
- id: userservice # 路由id,自定義,只要唯一即可
uri: lb://userservice # 路由的目標地址。lb就是負載均衡,后面跟服務(wù)名稱。
predicates: # 路由斷言。也就是判斷請求是否符合路由規(guī)則的條件。predicates譯為謂語、斷言
- Path=/user/** # 路徑斷言。這個是按照路徑匹配,只要以/user/開頭就符合要求
#之后訪問http://localhost:10010/user/1就路由(轉(zhuǎn)發(fā))到lb://userservice/1,注冊拉取并負載均衡后為http://localhost:8081
#第二組網(wǎng)關(guān)路由配置,針對于服務(wù)order-service
- id: order-service
uri: lb://order-service
predicates:
- Path=/order/**
我們將符合Path
規(guī)則的一切請求,都代理到 uri
參數(shù)指定的地址。
本例中,我們將 /user/**
開頭的請求,代理到lb://userservice
,lb是負載均衡,根據(jù)服務(wù)名拉取服務(wù)列表,實現(xiàn)負載均衡。
3.2.4.重啟測試
啟動gateway和userservice,訪問http://localhost:10010/user/1時,符合/user/**
規(guī)則,請求轉(zhuǎn)發(fā)到uri:http://userservice/user/1,得到了結(jié)果:
5)網(wǎng)關(guān)路由的流程圖
整個訪問的流程如下:
總結(jié):
網(wǎng)關(guān)搭建步驟:
- 創(chuàng)建項目,引入nacos服務(wù)發(fā)現(xiàn)和gateway依賴
- 配置application.yml,包括服務(wù)基本信息、nacos地址、路由
路由配置包括:
- 路由id:路由的唯一標示
- 路由目標(uri):路由的目標地址,http代表固定地址,lb代表根據(jù)服務(wù)名負載均衡
- 路由斷言(predicates):判斷路由的規(guī)則,
- 路由過濾器(filters):對請求或響應(yīng)做處理
接下來,就重點來學(xué)習(xí)路由斷言和路由過濾器的詳細知識
3.3.斷言工廠
我們只需要掌握Path這種路由工程就可以了。
predicates譯為斷言、謂語。
斷言是程序中的一階邏輯,目的為了表示與驗證軟件開發(fā)者預(yù)期的結(jié)果——當程序執(zhí)行到斷言的位置時,對應(yīng)的斷言應(yīng)該為真。若斷言不為真時,程序會中止執(zhí)行,并給出錯誤信息。
路由斷言。也就是判斷請求是否符合路由規(guī)則的條件。
斷言工廠Predicate Factory的作用:
讀取并處理yml配置的斷言規(guī)則,將斷言規(guī)則解析為路由判斷的條件
例如Path=/user/**是按照路徑匹配,這個規(guī)則是由
org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory
類來
處理的,像這樣的斷言工廠在SpringCloudGateway還有十幾個:
斷言工廠包括:?
名稱 | 說明 | 示例 |
---|---|---|
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 | 請求路徑必須符合指定規(guī)則 | - Path=/red/{segment},/blue/** |
Query | 請求參數(shù)必須包含指定參數(shù) | - Query=name, Jack或者- Query=name |
RemoteAddr | 請求者的ip必須是指定范圍 | - RemoteAddr=192.168.1.1/24 |
Weight | 權(quán)重處理 |
示例:
我們只需要掌握Path這種路由工程就可以了。
3.4.網(wǎng)關(guān)過濾器工廠
網(wǎng)關(guān)過濾器GatewayFilter是網(wǎng)關(guān)中提供的一種過濾器,可以對進入網(wǎng)關(guān)的請求和微服務(wù)返回的響應(yīng)做處理:
3.4.1.路由過濾器的種類
Spring提供了31種不同的路由過濾器工廠。例如:
名稱 | 說明 |
---|---|
AddRequestHeader | 給當前請求添加一個請求頭 |
RemoveRequestHeader | 移除請求中的一個請求頭 |
AddResponseHeader | 給響應(yīng)結(jié)果中添加一個響應(yīng)頭 |
RemoveResponseHeader | 從響應(yīng)結(jié)果中移除有一個響應(yīng)頭 |
RequestRateLimiter | 限制請求的流量 |
3.4.2.請求頭過濾器,AddRequestHeader
下面我們以AddRequestHeader 為例來講解。
需求:給所有進入userservice的請求添加一個請求頭:Truth=itcast is freaking awesome!
添加請求頭過濾器(局部):
只需要修改gateway服務(wù)的application.yml文件,給user-service添加路由過濾即可:
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://userservice
predicates:
- Path=/user/**
filters: # 過濾器
- AddRequestHeader=Truth, Itcast is freaking awesome! # 添加請求頭
當前過濾器寫在userservice路由下,因此僅僅對訪問userservice的請求有效。
參數(shù)注解@RequestHeader能獲取請求頭內(nèi)容:?
3.4.3.默認過濾器,default-filters
如果要對所有的路由都生效,則可以將過濾器工廠寫到default下。格式如下:
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://userservice
predicates:
- Path=/user/**
default-filters: # 默認過濾項
- AddRequestHeader=Truth, Itcast is freaking awesome!
3.4.4.總結(jié)
過濾器的作用是什么?
- ① 對路由的請求或響應(yīng)做加工處理,比如添加請求頭
- ② 配置在路由下的過濾器只對當前路由的請求生效
defaultFilters的作用是什么?
- ① 對所有路由都生效的過濾器
3.5.全局過濾器,GlobalFilter
上一節(jié)學(xué)習(xí)的過濾器,網(wǎng)關(guān)提供了31種,但每一種過濾器的作用都是固定的。如果我們希望攔截請求,做自己的業(yè)務(wù)邏輯則沒辦法實現(xiàn)。
3.5.1.全局過濾器作用
全局過濾器的作用也是處理一切進入網(wǎng)關(guān)的請求和響應(yīng),與GatewayFilter的作用一樣。
區(qū)別在于GatewayFilter通過配置定義,處理邏輯是固定的;而GlobalFilter的邏輯需要自己寫代碼實現(xiàn)。
定義方式是實現(xiàn)GlobalFilter接口。
public interface GlobalFilter {
/**
* 處理當前請求,有必要的話通過{@link GatewayFilterChain}將請求交給下一個過濾器處理
*
* @param exchange 請求上下文,里面可以獲取Request、Response等信息
* @param chain 用來把請求委托給下一個過濾器
* @return {@code Mono<Void>} 返回標示當前過濾器業(yè)務(wù)結(jié)束
*/
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
在filter中編寫自定義邏輯,可以實現(xiàn)下列功能:
- 登錄狀態(tài)判斷
- 權(quán)限校驗
- 請求限流等
3.5.2.自定義全局過濾器,過濾未登錄
需求:定義全局過濾器,攔截請求,判斷請求的參數(shù)是否滿足下面條件:
- 參數(shù)中是否有授權(quán)authorization,
- authorization參數(shù)值是否為admin
如果同時滿足則放行,否則攔截
實際開發(fā)中一般是從session或緩存等方式中獲取登錄信息,這里僅做學(xué)習(xí)全局過濾器。
實現(xiàn):
在gateway中定義一個過濾器:
package cn.itcast.gateway.filters;
//注解@Order設(shè)置過濾優(yōu)先級,越小優(yōu)先級越高。也可以實現(xiàn)Ordered接口的getOrder()方法
@Order(-1)
@Component
public class AuthorizeFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1.獲取請求參數(shù)。 MultiValueMap的add方法是一個key對應(yīng)多個值,set方法是一個key對應(yīng)一個值
MultiValueMap<String, String> params = exchange.getRequest().getQueryParams();
// 2.獲取authorization參數(shù)
String auth = params.getFirst("authorization");
// 3.校驗
if ("admin".equals(auth)) {
// 放行
return chain.filter(exchange);
}
// 4.攔截
// 4.1.禁止訪問,設(shè)置狀態(tài)碼,401未登錄unauthorized,
//exchange.getResponse().setRawStatusCode(401);
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
// 4.2.結(jié)束處理
return exchange.getResponse().setComplete();
}
}
回顧過濾器Filter:
@WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*") @Slf4j public class LoginCheckFilter implements Filter { public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher(); @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; String[] notFilter = new String[]{ "/employee/login", "/employee/logout", "/backend/**", "/front/**", "/common/**", "/user/sendMsg", "/user/login" }; String nowUri = request.getRequestURI(); // 放行不需要攔截的uri for(String url:notFilter){ if(PATH_MATCHER.match(url, nowUri)){ filterChain.doFilter(request,response); return; } } // session里有employee則已登錄,放行。針對于后端 if(request.getSession().getAttribute("employee")!=null){ BaseContext.setThreadId((Long) request.getSession().getAttribute("employee")); filterChain.doFilter(request,response); return; } //session里有user則已登錄,放行。針對于前端 if(request.getSession().getAttribute("user")!=null){ BaseContext.setThreadId((Long) request.getSession().getAttribute("user")); filterChain.doFilter(request,response); return; } // 未登錄返回錯誤信息,前端設(shè)置跳轉(zhuǎn) response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN"))); } }
3.5.3.過濾器執(zhí)行順序
請求進入網(wǎng)關(guān)會碰到三類過濾器:當前路由的過濾器、DefaultFilter、GlobalFilter
請求路由后,會將當前路由過濾器和DefaultFilter、GlobalFilter,合并到一個過濾器鏈(集合)中,排序后依次執(zhí)行每個過濾器:
排序的規(guī)則是什么呢?
- 每一個過濾器都必須指定一個int類型的order值,order值越小,優(yōu)先級越高,執(zhí)行順序越靠前。order值一樣時,defaultFilter > 路由過濾器 > GlobalFilter。
- GlobalFilter通過實現(xiàn)Ordered接口,或者添加@Order注解來指定order值,由我們自己指定
- 路由過濾器和defaultFilter的order由Spring指定,默認是按照聲明順序從1遞增。
- 當過濾器的order值一樣時,會按照 defaultFilter > 路由過濾器 > GlobalFilter的順序執(zhí)行。
詳細內(nèi)容,可以查看源碼:
org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters()
方法是先加載defaultFilters,然后再加載某個route的filters,然后合并。
org.springframework.cloud.gateway.handler.FilteringWebHandler#handle()
方法會加載全局過濾器,與前面的過濾器合并后根據(jù)order排序,組織過濾器鏈
3.6.跨域問題
3.6.1.什么是跨域問題
跨域:域名不一致就是跨域,主要包括:
- 域名不同: www.taobao.com 和 www.taobao.org 和 www.jd.com 和 miaosha.jd.com
- 域名相同,端口不同:localhost:8080和localhost8081
跨域問題:瀏覽器禁止請求的發(fā)起者與服務(wù)端發(fā)生跨域ajax請求,請求被瀏覽器攔截的問題
解決方案:CORS,跨域資源共享 CORS 詳解 - 阮一峰的網(wǎng)絡(luò)日志
CORS,全稱Cross-Origin Resource Sharing,即跨域資源共享?,是一種允許當前域(domain)的資源(比如html/js/web service)被其他域(domain)的腳本請求訪問的機制,通常由于同域安全策略(the same-origin security policy)瀏覽器會禁止這種跨域請求。
3.6.2.模擬跨域問題
頁面文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<pre>
spring:
cloud:
gateway:
globalcors: # 全局的跨域處理
add-to-simple-url-handler-mapping: true # 解決options請求被攔截問題
corsConfigurations:
'[/**]':
allowedOrigins: # 允許哪些網(wǎng)站的跨域請求
- "http://localhost:8090"
- "http://www.leyou.com"
allowedMethods: # 允許的跨域ajax的請求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允許在請求中攜帶的頭信息
allowCredentials: true # 是否允許攜帶cookie
maxAge: 360000 # 這次跨域檢測的有效期
</pre>
</body>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
axios.get("http://localhost:10010/user/1?authorization=admin")
.then(resp => console.log(resp.data))
.catch(err => console.log(err))
</script>
</html>
放入gateway的tomcat或者nginx這樣的web服務(wù)器中,啟動并訪問。
可以在瀏覽器控制臺看到下面的錯誤:
從localhost:8090訪問localhost:10010,端口不同,顯然是跨域的請求。文章來源:http://www.zghlxwxcb.cn/news/detail-443059.html
3.6.3.解決跨域問題,CORS
在gateway服務(wù)的application.yml文件中,添加下面的配置:文章來源地址http://www.zghlxwxcb.cn/news/detail-443059.html
spring:
cloud:
gateway:
# 。。。
globalcors: # 全局的跨域處理
add-to-simple-url-handler-mapping: true # 解決options請求被攔截問題
corsConfigurations:
'[/**]':
allowedOrigins: # 允許哪些網(wǎng)站的跨域請求
- "http://localhost:8090"
allowedMethods: # 允許的跨域ajax的請求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允許在請求中攜帶的頭信息
allowCredentials: true # 是否允許攜帶cookie
maxAge: 360000 # 這次跨域檢測的有效期
到了這里,關(guān)于SpringCloud基礎(chǔ)2——nacos配置、Feign、Gateway的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!