Spring Gateway核心概念、流程及原理

问题引入

通常,我们会使用nginx来做反向代理,转发到对应的后端服务。

但是,有时候,问题会很复杂,例如,我们需要限流、降级、熔断、认证、鉴权等等功能。

nginx能做这些工作吗?

答案是:当然能,我们可以使用OpenResty通过lua脚本来实现。

问题是:开发成本太高了,一些比较轻的业务还好,业务太多太重,lua脚本显然不合适。

有什么好的办法呢?

Spring Gateway,顾名思义,就是Spring提供的网关,限流、降级、熔断、认证、鉴权等等通用功能我们都可以做到网关层。

一句话:Nginx能做的,Spring Gateway都能做到,Nginx做不到的,Spring Gateway也能做。

核心流程

Spring Gateway核心是路由,路由有2个核心概念:

  1. Predicate:断言有的朋友也翻译为谓词,是路由匹配条件
  2. Filter:过滤器,是一系列中间的通用处理逻辑

我们通过一个配置来理解一下:

yaml 复制代码
server:
  port: 8000
spring:
  application:
    name: gateway-service-provider
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    gateway:
      discovery:
        locator:
          enabled: true
      routes:
        - id: feign-service
          uri: lb://feign-service-provider
          predicates:
            - Path=/api/feign/**
          filters:
            - StripPrefix=1
            - AddRequestParameter=name, tim
        - id: normal-http
          uri: http://localhost:8087
          predicates:
            - Path=/api/normal/**
          filters:
            - StripPrefix=1
            - AddResponseHeader=gateway-key, gateway-add-header
            - AddRequestHeader=X-Request-From, gateway

一个请求到了网关gateway会通过断言列表predicates来匹配,匹配成功会执行过滤器列表filters,然后将请求转发到uri对应的服务。

Spring Gateway支持服务和http请求。

使用lb需要添加依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

对于Spring Gateway注册中心不是必须的,这里要测试服务,所以使用了nacos作为注册中心,推荐使用nacos,因为eureka已经停止维护了,新版本的spring cloud已经不支持eureka了。

另外nacos还可以作为配置中心,如果我们想要实现动态路由,也可以通过nacos的配置中心实现。

配置原理解析

Spring Gateway的路由配置比较复杂,因为很多分灵活,有些参数比较多。

死记硬背肯定不是什么号的方式,有没有什么方法能解决这个问题呢?

理解它的原理和流程,知道怎么定位到它的具体实现,我们就需用去死记硬背了,需用的时候直接去看源码就可以。

不要被它的源码吓住,其实很简单,只需要抓住关键流程。

首先,我们要找的它的属性配置类,我们直接搜索spring.cloud.gateway,我们轻松找的了GatewayProperties类。

想知道具体为什么,需用属性Spring属性配置,不熟悉Spring属性配置的同学,可以先看一下这篇文章:Spring属性自动配置原理

简化后的核心代码如下:

java 复制代码
@ConfigurationProperties("spring.cloud.gateway")
public class GatewayProperties {

	private List<RouteDefinition> routes = new ArrayList<>();

	private List<FilterDefinition> defaultFilters = new ArrayList<>();
}

routes就是我们配置文件中的spring.cloud.gateway.routes列表,默认是一个空列表。

最重要的是我们知道了具体的抽象类型:RouteDefinition,跟进去:

java 复制代码
public class RouteDefinition {

	private String id;

	private List<PredicateDefinition> predicates = new ArrayList<>();

	private List<FilterDefinition> filters = new ArrayList<>();

	private URI uri;

	private Map<String, Object> metadata = new HashMap<>();

	private int order = 0;
}

非常简单,我们还是聚焦重点,predicates和filters,分别对应2个核心抽象类:

  1. PredicateDefinition:断言定义
  2. FilterDefinition:过滤器定义

它们的实现大差不差,我们以PredicateDefinition来说明一下。

java 复制代码
public class PredicateDefinition {

	private String name;

	private Map<String, String> args = new LinkedHashMap<>();

	public PredicateDefinition() {
	}

	public PredicateDefinition(String text) {
		int eqIdx = text.indexOf('=');
		if (eqIdx <= 0) {
			throw new ValidationException(
					"Unable to parse PredicateDefinition text '" + text + "'" + ", must be of the form name=value");
		}
		setName(text.substring(0, eqIdx));

		String[] args = tokenizeToStringArray(text.substring(eqIdx + 1), ",");

		for (int i = 0; i < args.length; i++) {
			this.args.put(NameUtils.generateName(i), args[i]);
		}
	}
}

问题在于:String:Path=/api/feign/** 怎么转换为:PredicateDefinition

答案是:org.springframework.core.convert.support.ObjectToObjectConverter

这个也是属性转换中使用了属性转换系统,最终使用了ObjectToObjectConverter,它会去找valueOf、of、from、构造函数等,看那个能做这种转换。

显然PredicateDefinition的构造函数,接收一个String,所以它能做转换。

所以不知道具体的predicates怎么配置,看一下RouteDefinition的构造函数的逻辑就可以:

  1. 通过=分割,第1部分作为name,第2部分作为value
  2. 再将value通过逗号分割,每一部分作为参数

有细心的朋友肯定会问,这只是定义啊,也不是最终实现啊。

别急,我们接着看。

从定义到实现关键核心流程

核心自动配置类与入口

我们已经从属性配置类知道了定义,该去到自动配置类GatewayAutoConfiguration了。

怎么找,这得属性Spring的自动配置,不熟悉Spring自动配置的朋友可以看看:SpringBoot自动配置原理

再继续之前,我们得先了解一下Spring Gateway的入口。

Spring Gateway现在使用的是响应式模式,使用的是WebFlux,而不是WebMvc。

WebFlux的一个重要类入口是:org.springframework.web.reactive.DispatcherHandler

看名字就知道,类似于DispatcherServlet,是用来做请求分发的,主要逻辑在handle方法中。

分发自然少不了HandlerMapping,就是url-->handler的映射。

Spring Gateway的最重要的组件是路由,所以有了:

org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping

RoutePredicateHandlerMapping又有2个重要的类:

  1. RouteLocator:顾名思义,路由定位器,用来定位路由的
  2. FilteringWebHandler:是一个WebHandler,是用来处理请求逻辑的,这里只是处理过滤器部分逻辑

这里,我们就不陷入具体的细节了,否则没完没了了。

想要了解细节的朋友,注意RoutePredicateHandlerMapping是一个HandlerMapping,关注它的核心方法getHandler

FilteringWebHandler是一个WebHandler,关注它的核心方法handle

RoutePredicateHandlerMapping

我们直接来看关联方法:

java 复制代码
protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
		return this.routeLocator.getRoutes()
				.concatMap(route -> Mono.just(route).filterWhen(r -> {
					exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId());
					return r.getPredicate().apply(exchange);
				}).next()
				.map(route -> {
					return route;
				});
	}

我们知道了重点是RouteLocator,那它是哪里来的呢?

当然是我们自动配置类GatewayAutoConfiguration中创建的:

java 复制代码
@Bean
@ConditionalOnMissingBean
public RoutePredicateHandlerMapping routePredicateHandlerMapping(FilteringWebHandler webHandler,
    RouteLocator routeLocator, GlobalCorsProperties globalCorsProperties, Environment environment) {
  return new RoutePredicateHandlerMapping(webHandler, routeLocator, globalCorsProperties, environment);
}

RouteDefinitionLocator与RouteLocator的魔法

那这个RouteLocator又是哪里来的呢?

我们不玩捉迷藏的游戏了,来看所有关键点:

java 复制代码
@Bean
public RouteLocator routeDefinitionRouteLocator(GatewayProperties properties,
    List<GatewayFilterFactory> gatewayFilters, List<RoutePredicateFactory> predicates,
    RouteDefinitionLocator routeDefinitionLocator, ConfigurationService configurationService) {
  return new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates, gatewayFilters, properties,
      configurationService);
}

@Bean
@Primary
@ConditionalOnMissingBean(name = "cachedCompositeRouteLocator")
public RouteLocator cachedCompositeRouteLocator(List<RouteLocator> routeLocators) {
  return new CachingRouteLocator(new CompositeRouteLocator(Flux.fromIterable(routeLocators)));
}

@Bean
@Primary
public RouteDefinitionLocator routeDefinitionLocator(List<RouteDefinitionLocator> routeDefinitionLocators) {
  return new CompositeRouteDefinitionLocator(Flux.fromIterable(routeDefinitionLocators));
}

@Bean
@ConditionalOnMissingBean
public PropertiesRouteDefinitionLocator propertiesRouteDefinitionLocator(GatewayProperties properties) {
  return new PropertiesRouteDefinitionLocator(properties);
}

@Bean
@ConditionalOnMissingBean(RouteDefinitionRepository.class)
public InMemoryRouteDefinitionRepository inMemoryRouteDefinitionRepository() {
  return new InMemoryRouteDefinitionRepository();
}

注意:RouteDefinitionLocator与RouteLocator的区别

  1. List:DiscoveryClientRouteDefinitionLocator,PropertiesRouteDefinitionLocator,InMemoryRouteDefinitionRepository -> CompositeRouteDefinitionLocator,注意没有其他的RouteDefinitionRepository才会创建InMemoryRouteDefinitionRepository
  2. CompositeRouteDefinitionLocator -> RouteDefinitionRouteLocator
  3. List:RouteDefinitionRouteLocator,我们自定义的@Bean RouteLocator -> CachingRouteLocator

因为@Primary所以routePredicateHandlerMapping优先使用:cachedCompositeRouteLocator

RouteDefinitionLocator在哪里啊

routeDefinitionLocator把所有的RouteDefinitionLocator列表收集起来,组合未了一个CompositeRouteDefinitionLocator。

所有的RouteDefinitionLocator,自然就包括了我们熟悉配置文件中来的PropertiesRouteDefinitionLocator。

这里顺带说一下Spring的@Bean对应List会把容器中所有的ClassType收集起来构建一个list注入进来。

想测试的朋友,可以看后面附录部分。

RouteDefinition怎么变装为Route

那终极问题:RouteDefinition怎么到Route的呢?

答案是:org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#convertToRoute方法

这其中还有一个关键点,涉及到我们自定义Predicate和GatewayFilter。

配置文件中的PredicateDefinition怎么转换为RoutePredicateFactory的,具体实现在:

org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#lookup方法。

配置文件中的FilterDefinition怎么转换为GatewayFilterFactory的,具体实现在:

org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#loadGatewayFilters方法。

经常会有朋友奇怪的Config参数是怎么注入的,也在lookup、loadGatewayFilters方法中实现。

其实很简单,记得我们前面创建RouteLocator的时候的List gatewayFilters, List predicates吗?

就是收集了Spring容器中所有的GatewayFilterFactory和RoutePredicateFactory,通过名字来查找。

GatewayFilterFactory和RoutePredicateFactory的诞生

它们在哪里创建的?

自然,还是在我们自动配置类GatewayAutoConfiguration之中。

java 复制代码
@Bean
@ConditionalOnEnabledFilter
public AddRequestParameterGatewayFilterFactory addRequestParameterGatewayFilterFactory() {
  return new AddRequestParameterGatewayFilterFactory();
}

@Bean
@ConditionalOnEnabledPredicate
public PathRoutePredicateFactory pathRoutePredicateFactory() {
  return new PathRoutePredicateFactory();
}

现在知道为什么我们自定义要实现GatewayFilterFactory,而不是GatewayFilter了吧。

实际调用服务的请求是怎么执行的呢?

有细心的朋友可能又会问了:上面的FilteringWebHandler都是filter,那实际执行服务请求在哪里啊?

答案是:就在Filter中。

Spring Gateway有2个特殊的全局Filter:

  1. NettyRoutingFilter:使用HttpClient,基于netty的响应式http客户端
  2. WebClientHttpRoutingFilter:基于Spring-WebFlux的WebClient,它的底层实现很多包括JDK、Netty的等

默认使用的是NettyRoutingFilter,它的优先级在Filter中是最低的,所以其他过滤器都执行完了,最好执行实际请求操作。

上面就是Spring Gateway相关的关键核心流程了。

现在,再有什么配置问题,或者要自定义断言,或者要自定义过滤器,那不是轻轻松松了吗?

当然,还有很多细节,只要我们弄清楚上面的核心流程,就不会有问题了。

默认过滤器与全局过滤器

例如,很多朋友混淆的默认过滤器和全局过滤器。

配置文件中配置的default-filters过滤器和GlobalFilter有什么区别?

yaml 复制代码
spring:
  cloud:
    gateway:
      default-filters:
      - AddResponseHeader=X-Response-Default-Red, Default-Blue
      - PrefixPath=/aaa
java 复制代码
public class GatewayProperties {
	private List<FilterDefinition> defaultFilters = new ArrayList<>();
}

其实,default-filters过滤器就相当于全局过滤器,因为所有GlobalFilter最终都会陪适配为GatewayFilter。

java 复制代码
public class GatewayAutoConfiguration {
  @Bean
  @ConditionalOnMissingBean
  public FilteringWebHandler filteringWebHandler(List<GlobalFilter> globalFilters) {
    return new FilteringWebHandler(globalFilters);
  }
}

public class FilteringWebHandler implements WebHandler {

	private final List<GatewayFilter> globalFilters;

	public FilteringWebHandler(List<GlobalFilter> globalFilters) {
		this.globalFilters = loadFilters(globalFilters);
	}
}

所以更灵活的方式就是自定义GatewayFilter,如果需要全局过滤器就把它配置在default-filters中。

自定义Predicate(断言、谓词)

我们知道原理,自定义就非常简单,结构比较固定,唯一需要注意的是Config配置类名字要固定。

java 复制代码
@Component
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config> {

    public MyRoutePredicateFactory() {
        super(Config.class);
    }

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {

        return exchange -> {
            ServerHttpRequest request = exchange.getRequest();
            // 构建一个Predicate,根据参数,判断是否匹配就可以
            return matches(config, request);
        };
    }

    public static class Config {
        //配置参数
    }
}

自定义GatewayFilter(路由过滤器)

前置过滤器

java 复制代码
@Component
public class PreGatewayFilterFactory extends AbstractGatewayFilterFactory<PreGatewayFilterFactory.Config> {

    public PreGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            // 注意exchange、request都是不可变的,所以使用mutate方法创建一个副本
            ServerHttpRequest.Builder builder = exchange.getRequest().mutate();
            // filter链之前处理前缀过滤逻辑具体过滤逻辑
            // 最后,调用后继过滤链
            return chain.filter(exchange.mutate().request(builder.build()).build());
        };
    }

    public static class Config {
        //配置属性
    }

}

后置过滤器

后置处理器,先执行filter链,然后在then中处理后置逻辑。

java 复制代码
@Component
public class PostGatewayFilterFactory extends AbstractGatewayFilterFactory<PostGatewayFilterFactory.Config> {

    public PostGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                ServerHttpResponse response = exchange.getResponse();
                // 处理response
            }));
        };
    }

    public static class Config {
        // 过滤器配置熟悉
    }
}

前后夹击

前置后置,只是为了区分执行时机,并不是说一个过滤器只能是前置或者只能是后置。

一个过滤器既可以包含执行请求前的逻辑,也可以包含请求后的逻辑。

最常用的,我们要记录请求时间,怎么办?

请求前记录时间,请求后计算时间。

java 复制代码
@Component
public class TimeGatewayFilterFactory extends AbstractGatewayFilterFactory<TimeGatewayFilterFactory.Config> {

    public TimeGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            exchange.getAttributes().put(COUNT_START_TIME, System.currentTimeMillis());
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                Long startTime = exchange.getAttribute(COUNT_START_TIME);
                StringBuilder sb = new StringBuilder(exchange.getRequest().getURI().getRawPath())
                                    .append("执行时间: ")
                                    .append(System.currentTimeMillis() - startTime)
                                    .append("ms");
                sb.append(" params:").append(exchange.getRequest().getQueryParams());
                log.info(sb.toString());
            }));
        };
    }

    public static class Config {
        // 过滤器配置熟悉
    }
}

自定义GlobalFilter(全局过滤器)

全局过滤器实现GlobalFilter, Ordered接口就可以,getOrder返回值越小,优先级越高。

java 复制代码
@Slf4j
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {

    private static final String AUTHORIZE_TOKEN = "token";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getQueryParams().getFirst( AUTHORIZE_TOKEN );
        if ( StringUtils.isBlank( token )) {
            log.info( "token is empty ..." );
            exchange.getResponse().setStatusCode( HttpStatus.UNAUTHORIZED );
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

Spring Gateway内置Predicate(断言、谓词)

Predicate相对简单,扫一眼知道有哪些功能即可,然后记住核心类:RoutePredicateFactory

需要的时候去找对应的实现类就可以。

谓词 配置示例 匹配条件
After After=2024-01-01T00:00:00.000+08:00 请求时间在指定时间之后
Before Before=2024-12-31T23:59:59.999+08:00 请求时间在指定时间之前
Between Between=2024-01-01T00:00:00.000+08:00,2024-12-31T23:59:59.999+08:00 请求时间在两个时间之间
Cookie Cookie=sessionId, abc.* 请求包含名为sessionId且值匹配正则abc.*的Cookie
Header Header=X-Request-Id, \d+ 请求包含名为X-Request-Id且值为数字的请求头
Host Host=**.oschool.vip 请求的Host头匹配**.oschool.vip模式
Method Method=GET,POST 请求方法为GET或POST
Path Path=/api/** 请求路径匹配/api/**模式
Query Query=id, \d+ 请求包含名为id且值为数字的查询参数
RemoteAddr RemoteAddr=192.168.0.1/24 请求来自指定IP段
Weight Weight=group1, 80 将80%的流量路由到该分组
XForwardedRemoteAddr - XForwardedRemoteAddr=192.168.1.1/24 请求头X-Forwarded-For的值匹配特定的ip网断
  1. AND操作:多个谓词默认以AND逻辑组合(必须全部满足)
  2. OR操作:可通过自定义谓词实现OR逻辑
  3. 取反 :可通过!前缀对谓词取反,如!Path=/admin/**

Spring Gateway内置Filter(过滤器)

太多了,不用全部记,扫一下有哪些功能,注意一下哪些常用的,其他的需要了再去看。

Spring Gateway常用过滤器(限流、熔断等)这篇也介绍了一些常用的过滤器,以及配置的原理。

随着版本的迭代,还可能会不断增加新的,还有三方实现的,记住核心接口GatewayFilterFactory就可以。

需要啥过滤器,先去扫一眼有没有现成的。

请求头滤器

过滤器名称 作用 简单示例
AddRequestHeader 添加请求头 - AddRequestHeader=X-Request-red, blue
FallbackHeaders 添加降级相关的请求头 - FallbackHeaders=
MapRequestHeader 映射请求头(复制到新请求头) - MapRequestHeader=Blue, X-Request-Red
PreserveHostHeader 保留原始请求的Host头 - PreserveHostHeader
RemoveRequestHeader 移除请求头 - RemoveRequestHeader=X-Request-Foo
SecureHeaders 添加安全相关的响应头 - SecureHeaders
SetRequestHeader 设置请求头(覆盖) - SetRequestHeader=X-Request-Red, Blue
AddRequestParameter 添加请求参数 - AddRequestParameter=red, blue
RemoveRequestAttribute 移除请求属性 - RemoveRequestAttribute=attr
RemoveRequestParameter 移除请求参数 - RemoveRequestParameter=blue
ModifyRequestBody 修改请求体 代码配置方式
SetRequestHostHeader 设置请求的Host头 - SetRequestHostHeader=example.com
PreserveHostHeader 保留原始host - PreserveHostHeader
JsonToGrpc json转gRpc,使用proto协议

修改响应

过滤器名称 作用 简单示例
RewriteLocationResponseHeader 重写响应中的Location头 - RewriteLocationResponseHeader=AS_IN_REQUEST, Location, ,
RemoveResponseHeader 移除响应头 - RemoveResponseHeader=X-Response-Foo
RewriteResponseHeader 重写响应头 - RewriteResponseHeader=X-Response-Red, , password=[^&]+, password=***
AddResponseHeader 添加响应头 - AddResponseHeader=X-Response-Red, Blue
DedupeResponseHeader 去除响应头中的重复值 - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
SetResponseHeader 设置响应头(覆盖) - SetResponseHeader=X-Response-Red, Blue
RemoveResponseCookie 移除响应Cookie - RemoveResponseCookie=cookieName
SaveSession 保存WebSession到下游请求 - SaveSession
ModifyResponseBody 修改响应体 代码配置方式
SetStatus 设置响应状态码 - SetStatus=401
RemoveJsonAttributesResponseBody 移除响应中的特定属性,默认只移除第1层,如果最后一个参数为true则移除所有指定属性 - RemoveJsonAttributesResponseBody=id,color,true

路径处理

过滤器名称 作用 简单示例
PrefixPath 为请求路径添加前缀 - PrefixPath=/user
SetPath 设置请求路径(支持模板) - SetPath=/{segment}
RewritePath 重写请求路径 - RewritePath=/red/(?<segment>.*), /$\{segment}
StripPrefix 去除请求路径前缀 - StripPrefix=2
RedirectTo 重定向到指定URL - RedirectTo=302, http://localhost:8087/ok

限制过滤器

过滤器名称 作用 简单示例
RequestSize 限制请求大小 - name: RequestSize args: maxSize: 5000000
RequestRateLimiter 请求速率限制 - name: RequestRateLimiter args: key-resolver: "#{@userKeyResolver}"

其他过滤器

过滤器名称 作用 简单示例
Retry 重试失败的请求 - name: Retry args: retries: 3 statuses: BAD_GATEWAY
TokenRelay OAuth2令牌中继 - TokenRelay=
CacheRequestBody 缓存请求体供后续使用 - CacheRequestBody=body
CircuitBreaker 熔断过滤,默认使用Resilience4J,需要添加相关依赖

各种动态路由实现(文件、数据库、配置中心、API)

为了避免这一篇的内容太多,动态路由的内容,写在了:各种动态路由实现

附录

配置参数怎么转换为配置类

前面我们已经介绍了断言和过滤器factory转换为Route是在RouteDefinitionRouteLocator中。

但是怎么处理配置的呢?

我们以过滤器为例来看一看:

在方法RouteDefinitionRouteLocator的loadGatewayFilters中:

java 复制代码
Object configuration = this.configurationService.with(factory)
      .name(definition.getName())
      .properties(definition.getArgs())
      .eventFunction((bound, properties) -> new FilterArgsEvent(
          RouteDefinitionRouteLocator.this, id, (Map<String, Object>) properties))
      .bind();

我们的GatewayFilterFactory必须同时实现Configurable和ShortcutConfigurable接口:

java 复制代码
public <T, C extends Configurable<T> & ShortcutConfigurable> ConfigurableBuilder<T, C> with(C configurable) {
  return new ConfigurableBuilder<T, C>(this, configurable);
}

T类型对应的就是实际的Config类。

java 复制代码
public interface Configurable<C> {

	Class<C> getConfigClass();

	C newConfig();

}

剩下的又是属性绑定Bind那一套东西,不过这里的属性使用的是过滤器中收集到参数,就是将key作为Confing的属性名,value作为对应值去创建Config对象。

@Bean List Set类型参数注入

java 复制代码
import lombok.Data;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;

@Configuration
public class Inject {

    @Bean
    public InjectConfig injectConfigA() {
        InjectConfig injectConfig = new InjectConfig();
        injectConfig.setId("A");
        return injectConfig;
    }

    @Bean
    public InjectConfig injectConfigB() {
        InjectConfig injectConfig = new InjectConfig();
        injectConfig.setId("B");
        return injectConfig;
    }

    @Bean
    public InjectConfig injectConfigC(List<InjectConfig> list) {
        InjectConfig injectConfig = new InjectConfig();
        injectConfig.setId("C");
        injectConfig.setList(list);
        return injectConfig;
    }

    @Data
    public static class InjectConfig {
        private String id;
        private List<InjectConfig> list;
    }
}

项目点仓库

示例代码仓库

http://localhost:8000/api/normal/ok

相关推荐
麦兜*1 天前
Spring Boot 启动过程全解析:从main方法到Tomcat启动的魔法之旅
java·spring boot·后端·spring·tomcat·firefox
清晓粼溪1 天前
SpringCloud-05-Micrometer Tracing+ZipKin分布式链路追踪
分布式·spring·spring cloud
cike_y1 天前
Spring整合Mybatis:dao层
java·开发语言·数据库·spring·mybatis
这是程序猿1 天前
基于java的SpringBoot框架医院药品管理系统
java·开发语言·spring boot·后端·spring·医院药品管理系统
麦兜*1 天前
Spring Boot 3.x 升级踩坑大全:Jakarta EE 9+、GraalVM Native 与配置迁移实战
java·spring boot·后端·spring·spring cloud
玄〤1 天前
Spring MVC 讲解:从初始化流程到请求参数与 JSON 处理全解析(黑马课程ssm笔记总结)(day5)
java·spring·json·mvc
、BeYourself1 天前
PGvector :在 Spring AI 中实现向量数据库存储与相似性搜索
数据库·人工智能·spring·springai
冷雨夜中漫步1 天前
Spring Cloud入门—— (1)Spring Cloud Alibaba生态组件Nacos3.0本地部署
后端·spring·spring cloud