问题引入
通常,我们会使用nginx来做反向代理,转发到对应的后端服务。
但是,有时候,问题会很复杂,例如,我们需要限流、降级、熔断、认证、鉴权等等功能。
nginx能做这些工作吗?
答案是:当然能,我们可以使用OpenResty通过lua脚本来实现。
问题是:开发成本太高了,一些比较轻的业务还好,业务太多太重,lua脚本显然不合适。
有什么好的办法呢?
Spring Gateway,顾名思义,就是Spring提供的网关,限流、降级、熔断、认证、鉴权等等通用功能我们都可以做到网关层。
一句话:Nginx能做的,Spring Gateway都能做到,Nginx做不到的,Spring Gateway也能做。
核心流程
Spring Gateway核心是路由,路由有2个核心概念:
- Predicate:断言有的朋友也翻译为谓词,是路由匹配条件
- 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个核心抽象类:
- PredicateDefinition:断言定义
- 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部分作为name,第2部分作为value
- 再将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个重要的类:
- RouteLocator:顾名思义,路由定位器,用来定位路由的
- 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的区别
- List:DiscoveryClientRouteDefinitionLocator,PropertiesRouteDefinitionLocator,InMemoryRouteDefinitionRepository -> CompositeRouteDefinitionLocator,注意没有其他的RouteDefinitionRepository才会创建InMemoryRouteDefinitionRepository
- CompositeRouteDefinitionLocator -> RouteDefinitionRouteLocator
- 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:
- NettyRoutingFilter:使用HttpClient,基于netty的响应式http客户端
- 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网断 |
- AND操作:多个谓词默认以AND逻辑组合(必须全部满足)
- OR操作:可通过自定义谓词实现OR逻辑
- 取反 :可通过
!前缀对谓词取反,如!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;
}
}