学习目标:
- soul bootstrap 的插件链
学习内容:
上篇文章介绍了soul实现热插拔的原理,今天学习一下源码,看看新增插件/数据程序是如何运行的
学习时间:
2021年1月31号
学习产出:
soul 网关运行时接收流量的组件是 soul bootstrap, 这个工程是基于 spring-webflux 开发的响应式的 Java Web 应用, soul 网关的插件通过组成插件链的形式, 让所有进入 web 应用的请求都经过一次插件链。
soul bootstrap 的工程依赖中加入了所有 starter 形式的可用的插件信息,通过运行时数据同步的方式从 soul admin 信息中同步影响插件行为的数据,这些数据存放在了 JVM 缓存当中。
soul bootstrap 运行时相关的配置在 soul-web 模块中加载,这个模块定义了 soul bootstrap 运行时的相关功能,比如统一异常处理,统一处理过滤器,部分 RPC 类型参数转化等,soul-web 包含了一些配置类,
其中比较关键的类是
org.dromara.soul.web.configuration.SoulConfiguration
@Configuration
@ComponentScan("org.dromara.soul")
@Import(value = {ErrorHandlerConfiguration.class, SoulExtConfiguration.class, SpringExtConfiguration.class})
@Slf4j
public class SoulConfiguration {
/**
* Init SoulWebHandler.
*
* @param plugins this plugins is All impl SoulPlugin.
* @return {@linkplain SoulWebHandler}
*/
@Bean("webHandler")
public SoulWebHandler soulWebHandler(final ObjectProvider<List<SoulPlugin>> plugins) {
List<SoulPlugin> pluginList = plugins.getIfAvailable(Collections::emptyList);
final List<SoulPlugin> soulPlugins = pluginList.stream()
.sorted(Comparator.comparingInt(SoulPlugin::getOrder)).collect(Collectors.toList());
soulPlugins.forEach(soulPlugin -> log.info("load plugin:[{}] [{}]", soulPlugin.named(), soulPlugin.getClass().getName()));
return new SoulWebHandler(soulPlugins);
}
/**
* init dispatch handler.
*
* @return {@link DispatcherHandler}.
*/
@Bean("dispatcherHandler")
public DispatcherHandler dispatcherHandler() {
return new DispatcherHandler();
}
/**
* Plugin data subscriber plugin data subscriber.
*
* @param pluginDataHandlerList the plugin data handler list
* @return the plugin data subscriber
*/
@Bean
public PluginDataSubscriber pluginDataSubscriber(final ObjectProvider<List<PluginDataHandler>> pluginDataHandlerList) {
return new CommonPluginDataSubscriber(pluginDataHandlerList.getIfAvailable(Collections::emptyList));
}
/**
* Generic param resolve service generic param resolve service.
*
* @return the generic param resolve service
*/
@Bean
@ConditionalOnProperty(name = "soul.dubbo.parameter", havingValue = "multi")
public DubboParamResolveService dubboMultiParameterResolveServiceImpl() {
return new DubboMultiParameterResolveServiceImpl();
}
/**
* Generic param resolve service dubbo param resolve service.
*
* @return the dubbo param resolve service
*/
@Bean
@ConditionalOnMissingBean(value = DubboParamResolveService.class, search = SearchStrategy.ALL)
public DubboParamResolveService defaultDubboParamResolveService() {
return new DefaultDubboParamResolveService();
}
/**
* Remote address resolver remote address resolver.
*
* @return the remote address resolver
*/
@Bean
@ConditionalOnMissingBean(RemoteAddressResolver.class)
public RemoteAddressResolver remoteAddressResolver() {
return new ForwardedRemoteAddressResolver(1);
}
/**
* Cross filter web filter.
* if you application has cross-domain.
* this is demo.
* 1. Customize webflux's cross-domain requests.
* 2. Spring bean Sort is greater than -1.
*
* @return the web filter
*/
@Bean
@Order(-100)
@ConditionalOnProperty(name = "soul.cross.enabled", havingValue = "true")
public WebFilter crossFilter() {
return new CrossFilter();
}
/**
* Body web filter web filter.
*
* @param soulConfig the soul config
* @return the web filter
*/
@Bean
@Order(-10)
@ConditionalOnProperty(name = "soul.file.enabled", havingValue = "true")
public WebFilter fileSizeFilter(final SoulConfig soulConfig) {
return new FileSizeFilter(soulConfig.getFileMaxSize());
}
/**
* Soul config soul config.
*
* @return the soul config
*/
@Bean
@ConfigurationProperties(prefix = "soul")
public SoulConfig soulConfig() {
return new SoulConfig();
}
/**
* Init time web filter.
*
* @param soulConfig the soul config
* @return {@linkplain TimeWebFilter}
*/
@Bean
@Order(30)
@ConditionalOnProperty(name = "soul.filterTimeEnable")
public WebFilter timeWebFilter(final SoulConfig soulConfig) {
return new TimeWebFilter(soulConfig);
}
/**
* Web socket web filter web filter.
*
* @return the web filter
*/
@Bean
@Order(4)
public WebFilter webSocketWebFilter() {
return new WebSocketParamFilter();
}
}
Spring 容器加载 classpath 中 com.dromara.soul 包路径下的所有 Bean, 这样就把 soul 运行时的所需要的Spring Bean 全部加载到容器中, 配置中还有一些 Filter 的定义, 这些 Filter 定义是部署级别的可选用, 即通过 Spring 应用的配置可以选择开启部分 Filter 的功能。
上面比较重要的配置 Bean 是 SoulWebHandler 的配置,可以看到这个类的构造函数需要传入 SoulPlugin 的列表对象:
public final class SoulWebHandler implements WebHandler {
private final List<SoulPlugin> plugins;
private final Scheduler scheduler;
/**
* Instantiates a new Soul web handler.
*
* @param plugins the plugins
*/
public SoulWebHandler(final List<SoulPlugin> plugins) {
this.plugins = plugins;
String schedulerType = System.getProperty("soul.scheduler.type", "fixed");
if (Objects.equals(schedulerType, "fixed")) {
int threads = Integer.parseInt(System.getProperty(
"soul.work.threads", "" + Math.max((Runtime.getRuntime().availableProcessors() << 1) + 1, 16)));
scheduler = Schedulers.newParallel("soul-work-threads", threads);
} else {
scheduler = Schedulers.elastic();
}
}
/**
* Handle the web server exchange.
*
* @param exchange the current server exchange
* @return {@code Mono<Void>} to indicate when request handling is complete
*/
@Override
public Mono<Void> handle(@NonNull final ServerWebExchange exchange) {
MetricsTrackerFacade.getInstance().counterInc(MetricsLabelEnum.REQUEST_TOTAL.getName());
Optional<HistogramMetricsTrackerDelegate> startTimer = MetricsTrackerFacade.getInstance().histogramStartTimer(MetricsLabelEnum.REQUEST_LATENCY.getName());
return new DefaultSoulPluginChain(plugins).execute(exchange).subscribeOn(scheduler)
.doOnSuccess(t -> startTimer.ifPresent(time -> MetricsTrackerFacade.getInstance().histogramObserveDuration(time)));
}
private static class DefaultSoulPluginChain implements SoulPluginChain {
private int index;
private final List<SoulPlugin> plugins;
/**
* Instantiates a new Default soul plugin chain.
*
* @param plugins the plugins
*/
DefaultSoulPluginChain(final List<SoulPlugin> plugins) {
this.plugins = plugins;
}
/**
* Delegate to the next {@code WebFilter} in the chain.
*
* @param exchange the current server exchange
* @return {@code Mono<Void>} to indicate when request handling is complete
*/
@Override
public Mono<Void> execute(final ServerWebExchange exchange) {
return Mono.defer(() -> {
if (this.index < plugins.size()) {
SoulPlugin plugin = plugins.get(this.index++);
Boolean skip = plugin.skip(exchange);
if (skip) {
return this.execute(exchange);
}
return plugin.execute(exchange, this);
}
return Mono.empty();
});
}
}
}
可以看到 SoulPlugin 的列表对象构建了 org.dromara.soul.web.handler.SoulWebHandler.DefaultSoulPluginChain 实例对象影响了插件运行时的行为。
本文解析了SoulBootstrap如何基于Spring-webflux实现热插拔,介绍其如何通过插件链处理请求,并重点讲解了SoulConfiguration中的关键组件及其作用。
1633

被折叠的 条评论
为什么被折叠?



