SpringCloud Gateway加載斷言predicates與過濾器filters的源碼分析
我們今天的主角是Gateway網(wǎng)關(guān),一聽名字就知道它基本的任務(wù)就是去分發(fā)路由。根據(jù)不同的指定名稱去請求各個(gè)服務(wù),下面是Gateway官方的解釋:
https://spring.io/projects/spring-cloud-gateway,其他的博主就不多說了,大家多去官網(wǎng)看看,只有官方的才是最正確的,回歸主題,我們的過濾器與斷言如何加載進(jìn)來的,并且是如何進(jìn)行對請求進(jìn)行過濾的。
大家如果對SpringBoot自動加載的熟悉的話,一定知道要看一個(gè)代碼的源碼,要找到META-INF下的spring.factories,具體為啥的博主就不多說了,網(wǎng)上也有很多講解自動加載的源碼分析,今天就講解Gateway,所有項(xiàng)目三板斧:加依賴、寫注解、弄配置
依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
注解:啟動類上需要添加@EnableDiscoveryClient,啟動服務(wù)發(fā)現(xiàn)
配置:
spring:
cloud:
gateway:
routes:
- id: after-route #id必須要唯一
uri: lb://product-center
predicates:
- After=2030-12-16T15:53:22.999+08:00[Asia/Shanghai]
filters:
- PrefixPath=/product-api
大家看到這個(gè)配置的時(shí)候,為什么我們寫After斷言與PrefixPath過濾器,gateway就會自動識別呢,那我們有沒有那一個(gè)地方可以看到所有的自帶的屬性呢?當(dāng)然有,而且我們本篇就主要講解為什么gateway會自動識別,并且我們要自己實(shí)現(xiàn)并且添加自定義屬性。開始源碼解析第一步,找到自動加載的類一探究竟;

看到這里的時(shí)候,第一步就成功了,剩下的就是找到org.springframework.cloud.gateway.config.GatewayAutoConfiguration這個(gè)關(guān)鍵類,我們主要看看里面的兩個(gè)類
@Bean
public RouteLocator routeDefinitionRouteLocator(GatewayProperties properties,
List<GatewayFilterFactory> GatewayFilters,
List<RoutePredicateFactory> predicates,
RouteDefinitionLocator routeDefinitionLocator) {
return new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates, GatewayFilters, properties);
}
@Bean
@Primary
//TODO: property to disable composite?
public RouteLocator cachedCompositeRouteLocator(List<RouteLocator> routeLocators) {
return new CachingRouteLocator(new CompositeRouteLocator(Flux.fromIterable(routeLocators)));
}
這倆個(gè)類配置,大家可能非常熟悉,大家上手一個(gè)新知識點(diǎn)的時(shí)候,肯定會找一些快速入門的文章看看,博主還是習(xí)慣直接找官方的quick start來看,大家可以看看這些快速上手項(xiàng)目:https://spring.io/guides/gs/gateway/
所以博主直接就找到了RouteLocator這個(gè)類配置,果不其然,我們找到了斷言與過濾器的注入,雖然實(shí)在方法體內(nèi)作為參數(shù)傳入,但是會被spring解析到,直接去工廠里拿到,具體怎么拿呢?我們再來看看:
public BeanWrapper instantiateUsingFactoryMethod(
String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {
.....
for (Method candidate : candidates) {
Class<?>[] paramTypes = candidate.getParameterTypes();
if (paramTypes.length >= minNrOfArgs) {
ArgumentsHolder argsHolder;
if (explicitArgs != null) {
// Explicit arguments given -> arguments length must match exactly.
if (paramTypes.length != explicitArgs.length) {
continue;
}
argsHolder = new ArgumentsHolder(explicitArgs);
}
else {
// Resolved constructor arguments: type conversion and/or autowiring necessary.
try {
String[] paramNames = null;
ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer();
if (pnd != null) {
paramNames = pnd.getParameterNames(candidate);
}
//主要就是會進(jìn)入到這里去解析每一個(gè)參數(shù)類型
argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw,
paramTypes, paramNames, candidate, autowiring, candidates.length == 1);
}
catch (UnsatisfiedDependencyException ex) {
if (logger.isTraceEnabled()) {
logger.trace("Ignoring factory method [" + candidate + "] of bean '" + beanName + "': " + ex);
}
// Swallow and try next overloaded factory method.
if (causes == null) {
causes = new LinkedList<>();
}
causes.add(ex);
continue;
}
}
int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));
// Choose this factory method if it represents the closest match.
if (typeDiffWeight < minTypeDiffWeight) {
factoryMethodToUse = candidate;
argsHolderToUse = argsHolder;
argsToUse = argsHolder.arguments;
minTypeDiffWeight = typeDiffWeight;
ambiguousFactoryMethods = null;
}
// Find out about ambiguity: In case of the same type difference weight
// for methods with the same number of parameters, collect such candidates
// and eventually raise an ambiguity exception.
// However, only perform that check in non-lenient constructor resolution mode,
// and explicitly ignore overridden methods (with the same parameter signature).
else if (factoryMethodToUse != null && typeDiffWeight == minTypeDiffWeight &&
!mbd.isLenientConstructorResolution() &&
paramTypes.length == factoryMethodToUse.getParameterCount() &&
!Arrays.equals(paramTypes, factoryMethodToUse.getParameterTypes())) {
if (ambiguousFactoryMethods == null) {
ambiguousFactoryMethods = new LinkedHashSet<>();
ambiguousFactoryMethods.add(factoryMethodToUse);
}
ambiguousFactoryMethods.add(candidate);
}
}
}
.....
return bw;
}
每一個(gè)參數(shù)都需要解析,但是看這里不像沒關(guān)系,繼續(xù)往下走:就會看到
private ArgumentsHolder createArgumentArray(
String beanName, RootBeanDefinition mbd, @Nullable ConstructorArgumentValues resolvedValues,
BeanWrapper bw, Class<?>[] paramTypes, @Nullable String[] paramNames, Executable executable,
boolean autowiring, boolean fallback) throws UnsatisfiedDependencyException {
....
//這下就是了,每個(gè)參數(shù)都被進(jìn)行解析
for (int paramIndex = 0; paramIndex < paramTypes.length; paramIndex++) {
....
try {
//我們的參數(shù)就是在這里被進(jìn)行解析的--resolveAutowiredArgument
Object autowiredArgument = resolveAutowiredArgument(
methodParam, beanName, autowiredBeanNames, converter, fallback);
args.rawArguments[paramIndex] = autowiredArgument;
args.arguments[paramIndex] = autowiredArgument;
args.preparedArguments[paramIndex] = new AutowiredArgumentMarker();
args.resolveNecessary = true;
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(
mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam), ex);
}
}
}
//其他不重要的,直接忽略掉
...
return args;
}
開始解析的時(shí)看到了,我們需要把斷言和過濾器列表都加在進(jìn)來,那spring是如何加載的呢?是根據(jù)方法體內(nèi)傳入的類型找到所有實(shí)現(xiàn)了斷言和過濾器工廠接口的類并且進(jìn)行獲取實(shí)例,我們仔細(xì)這些工廠的實(shí)現(xiàn)類,就會找到我們的使用的一些屬性,比如我們例子中的PrefixPath過濾器和Path斷言;
protected Map<String, Object> findAutowireCandidates(
@Nullable String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {
//主要的就是這個(gè),beanNamesForTypeIncludingAncestors方法,該方法就是從bean工廠中獲取所有當(dāng)前類的實(shí)現(xiàn)實(shí)例名稱,
String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this, requiredType, true, descriptor.isEager());
Map<String, Object> result = new LinkedHashMap<>(candidateNames.length);
...
//遍歷名稱,進(jìn)行實(shí)例化
for (String candidate : candidateNames) {
if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, descriptor)) {
addCandidateEntry(result, candidate, descriptor, requiredType);
}
}
.....
return result;
}


這下我們知道了,系統(tǒng)配置的斷言和過濾器是如何被加載 的了,那我們還有一個(gè)問題,如果我自定義一個(gè),如何被系統(tǒng)識別呢?并且怎么進(jìn)行配置呢?不難發(fā)現(xiàn)我們之前看源碼時(shí),他是被spring通過找工廠實(shí)現(xiàn)類找到并且加載進(jìn)來的,那我們自己實(shí)現(xiàn)工廠接口并且使用@Component注解,讓spring加載進(jìn)來不就的了嗎?但是你會發(fā)現(xiàn)系統(tǒng)自定義的屬性斷言或者過濾器都有工廠名字的后綴,這是為什么呢?影響我們自定義 的類被加載到gateway中且生效嗎?事實(shí)是會影響,那為什么影響呢?我們還是看源碼。因?yàn)槲覀冎暗念惣虞d還沒有看完,我們最開始的時(shí)候就找到了兩個(gè)@bean 的自動加載,那這兩個(gè)類實(shí)例化的時(shí)候都做了哪些工作,我們還沒有細(xì)看;
public RouteDefinitionRouteLocator(RouteDefinitionLocator routeDefinitionLocator,
List<RoutePredicateFactory> predicates,
List<GatewayFilterFactory> gatewayFilterFactories,
GatewayProperties gatewayProperties) {
this.routeDefinitionLocator = routeDefinitionLocator;
initFactories(predicates);
gatewayFilterFactories.forEach(factory -> this.gatewayFilterFactories.put(factory.name(), factory));
this.gatewayProperties = gatewayProperties;
}
initFactories(predicates):這段代碼主要是進(jìn)行解析斷言工廠實(shí)現(xiàn)類;并且放入一個(gè)Map中,
gatewayFilterFactories.forEach(factory -> this.gatewayFilterFactories.put(factory.name(), factory)):跟斷言的代碼幾乎一樣,因?yàn)闆]有其他多余的邏輯,所以沒有封裝到方法中,直接使用java8 的流特性,寫完了遍歷的過程。大家要注意一段代碼就是factory.name(),這里使用了一個(gè)方法;
default String name() {
return NameUtils.normalizeRoutePredicateName(getClass());
}
主要就是把當(dāng)前類包含工廠名字的部分去掉了,然后用剩下的字符串當(dāng)key值,所以我們可以使用工廠名字做后墜,也可以不用,但是剩下的字符則是你要寫進(jìn)配置的關(guān)鍵字,不過博主基本都是按照系統(tǒng)自帶屬性一樣,用的是工廠接口的名字做的后綴。
好了,今天就講解這么多,下次在講解gateway接到請求后,是如何進(jìn)行一步一步過濾的,何時(shí)進(jìn)行斷言校驗(yàn)的。一次不講這么多,消化了就好。
到此這篇關(guān)于SpringCloud Gateway加載斷言predicates與過濾器filters的源碼分析的文章就介紹到這了,更多相關(guān)SpringCloud Gateway斷言內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot集成MyBatisPlus+MySQL的實(shí)現(xiàn)
MybatisPlus是國產(chǎn)的第三方插件, 它封裝了許多常用的CURDapi,免去了我們寫mapper.xml的重復(fù)勞動,本文主要介紹了SpringBoot集成MyBatisPlus+MySQL的實(shí)現(xiàn),感興趣的可以了解一下2023-10-10
springboot?如何解決yml沒有spring的小葉子標(biāo)志問題
這篇文章主要介紹了springboot?如何解決yml沒有spring的小葉子標(biāo)志問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
Java Socket+多線程實(shí)現(xiàn)多人聊天室功能
這篇文章主要為大家詳細(xì)介紹了Java Socket+多線程實(shí)現(xiàn)多人聊天室功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-07-07
基于Java的Socket多客戶端Client-Server聊天程序的實(shí)現(xiàn)
這篇文章主要介紹了基于Java的Socket多客戶端Client-Server聊天程序的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03

