Introduction to the
Today we’ll explore the initialization of the plug-in and the loading of the associated configuration
Source code for the Debug
Plug-in initialization
Let’s start with the familiar plug-in chain invocation class: SoulWebHandler, where DefaultSoulPluginChain shows that plugins are passed in through the constructor
Let’s track who passed it in, set a breakpoint on the public line of SoulWebHandler, and restart to trace the call stack
public final class SoulWebHandler implements WebHandler {
private final List<SoulPlugin> plugins;
// You can also see here that plugins are passed in via the constructor, put a breakpoint on the public line, and then restart
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(); }}@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());
// Pass plugins to DefaultSoulPluginChain
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;
// Use the constructor to get plugins
DefaultSoulPluginChain(final List<SoulPlugin> plugins) {
this.plugins = plugins;
}
@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);
}
returnMono.empty(); }); }}}Copy the code
I’m a little slow here, so after we reboot and wait, we look at the last call and we’re at SoulConfiguration
The general information about the class shows that it is a Spring like loading mechanism. According to the analysis of the previous article, it should be roughly configured with auto Configuration and then loaded by Spring at startup
public class SoulConfiguration {
@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 newSoulWebHandler(soulPlugins); }}Copy the code
It is generally known that it is automatically loaded by Spring. Through the AutoConfiguration mechanism, the following is related to Spring, but I will not continue to trace here
Configuration initialization
In the analysis of the previous article, we analyzed the rough details of route matching, requiring the configuration of selectors and rules. Let’s track the initialization of these configurations
First, enter the core class of route matching: AbstractSoulPlugin, in which pluginData, selectorData and rules are obtained, as shown in the following code comments:
public abstract class AbstractSoulPlugin implements SoulPlugin {
@Override
public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
String pluginName = named();
// Get plug-in data
final PluginData pluginData = BaseDataCache.getInstance().obtainPluginData(pluginName);
if(pluginData ! =null && pluginData.getEnabled()) {
// Get selector data based on the plug-in name
final Collection<SelectorData> selectors = BaseDataCache.getInstance().obtainSelectorData(pluginName);
if (CollectionUtils.isEmpty(selectors)) {
return handleSelectorIsNull(pluginName, exchange, chain);
}
final SelectorData selectorData = matchSelector(exchange, selectors);
if (Objects.isNull(selectorData)) {
return handleSelectorIsNull(pluginName, exchange, chain);
}
selectorLog(selectorData, pluginName);
// Get the rule data of the selector based on its ID
final List<RuleData> rules = BaseDataCache.getInstance().obtainRuleData(selectorData.getId());
if (CollectionUtils.isEmpty(rules)) {
return handleRuleIsNull(pluginName, exchange, chain);
}
RuleData rule;
if (selectorData.getType() == SelectorTypeEnum.FULL_FLOW.getCode()) {
rule = rules.get(rules.size() - 1);
} else {
rule = matchRule(exchange, rules);
}
if (Objects.isNull(rule)) {
return handleRuleIsNull(pluginName, exchange, chain);
}
ruleLog(rule, pluginName);
return doExecute(exchange, chain, selectorData, rule);
}
returnchain.execute(exchange); }}Copy the code
Take a look at the class BaseDataCache and see that it has a Map store of two data: plug-in data, selector data, and rule data
public final class BaseDataCache {
/** * pluginName -> PluginData. */
private static final ConcurrentMap<String, PluginData> PLUGIN_MAP = Maps.newConcurrentMap();
/** * pluginName -> SelectorData. */
private static final ConcurrentMap<String, List<SelectorData>> SELECTOR_MAP = Maps.newConcurrentMap();
/** * selectorId -> RuleData. */
private static final ConcurrentMap<String, List<RuleData>> RULE_MAP = Maps.newConcurrentMap();
}
Copy the code
We find that it is a static singleton, and the plug-in data is configured using the following function. We break the function to see who called it and what its call stack looks like
public final class BaseDataCache {
public void cachePluginData(final PluginData pluginData) { Optional.ofNullable(pluginData).ifPresent(data -> PLUGIN_MAP.put(data.getName(), data)); }}Copy the code
On the public line, click a breakpoint, restart, and go to the pluginData function
PluginData(id=1, name=sign, config=null, role=1, enabled=false)
Copy the code
We trace its call stack, and its call chain of classes and functions is as follows, and the order of the classes in the call stack is also corresponding
public class CommonPluginDataSubscriber implements PluginDataSubscriber {
// Inside this triggers the subscribeDataHandler, which triggers the DaseDataCache call
@Override
public void onSubscribe(final PluginData pluginData) {
subscribeDataHandler(pluginData, DataEventTypeEnum.UPDATE);
}
private <T> void subscribeDataHandler(final T classData, final DataEventTypeEnum dataType) {
Optional.ofNullable(classData).ifPresent(data -> {
// Plug-in data manipulation
if (data instanceof PluginData) {
PluginData pluginData = (PluginData) data;
if (dataType == DataEventTypeEnum.UPDATE) {
// Make the trigger call here
BaseDataCache.getInstance().cachePluginData(pluginData);
Optional.ofNullable(handlerMap.get(pluginData.getName())).ifPresent(handler -> handler.handlerPlugin(pluginData));
} else if (dataType == DataEventTypeEnum.DELETE) {
BaseDataCache.getInstance().removePluginData(pluginData);
Optional.ofNullable(handlerMap.get(pluginData.getName())).ifPresent(handler -> handler.removePlugin(pluginData));
}
// Selector operation
} else if (data instanceof SelectorData) {
SelectorData selectorData = (SelectorData) data;
if (dataType == DataEventTypeEnum.UPDATE) {
BaseDataCache.getInstance().cacheSelectData(selectorData);
Optional.ofNullable(handlerMap.get(selectorData.getPluginName())).ifPresent(handler -> handler.handlerSelector(selectorData));
} else if (dataType == DataEventTypeEnum.DELETE) {
BaseDataCache.getInstance().removeSelectData(selectorData);
Optional.ofNullable(handlerMap.get(selectorData.getPluginName())).ifPresent(handler -> handler.removeSelector(selectorData));
}
// Operation of the rule
} else if (data instanceof RuleData) {
RuleData ruleData = (RuleData) data;
if (dataType == DataEventTypeEnum.UPDATE) {
BaseDataCache.getInstance().cacheRuleData(ruleData);
Optional.ofNullable(handlerMap.get(ruleData.getPluginName())).ifPresent(handler -> handler.handlerRule(ruleData));
} else if(dataType == DataEventTypeEnum.DELETE) { BaseDataCache.getInstance().removeRuleData(ruleData); Optional.ofNullable(handlerMap.get(ruleData.getPluginName())).ifPresent(handler -> handler.removeRule(ruleData)); }}}); }}// This class triggers the configuration of the single plug-in above, and this class gets all the plug-in data
public class PluginDataHandler extends AbstractDataHandler<PluginData> {
@Override
protected void doRefresh(final List<PluginData> dataList) {
// Trigger the above callpluginDataSubscriber.refreshPluginDataSelf(dataList); dataList.forEach(pluginDataSubscriber::onSubscribe); }}// Follow along to see the source of datalist
public abstract class AbstractDataHandler<T> implements DataHandler {
@Override
public void handle(final String json, final String eventType) {
List<T> dataList = convert(json);
if (CollectionUtils.isNotEmpty(dataList)) {
DataEventTypeEnum eventTypeEnum = DataEventTypeEnum.acquireByName(eventType);
switch (eventTypeEnum) {
case REFRESH:
case MYSELF:
// trigger the above function call
doRefresh(dataList);
break;
case UPDATE:
case CREATE:
doUpdate(dataList);
break;
case DELETE:
doDelete(dataList);
break;
default:
break; }}}}// Get to websocket
public class WebsocketDataHandler {
public void executor(final ConfigGroupEnum type, final String json, final String eventType) { ENUM_MAP.get(type).handle(json, eventType); }}// In this class, you can see that the plug-in data list comes from websocket, which is the data from soul-admin
public final class SoulWebsocketClient extends WebSocketClient {
public void onMessage(final String result) {
handleResult(result);
}
@SuppressWarnings("ALL")
private void handleResult(final String result) { WebsocketData websocketData = GsonUtils.getInstance().fromJson(result, WebsocketData.class); ConfigGroupEnum groupEnum = ConfigGroupEnum.acquireByName(websocketData.getGroupType()); String eventType = websocketData.getEventType(); String json = GsonUtils.getInstance().toJson(websocketData.getData()); websocketDataHandler.executor(groupEnum, json, eventType); }}Copy the code
Take a look at the general content of result: some simple configuration
{
"groupType": "PLUGIN"."eventType": "MYSELF"."data": [{
"id": "1"."name": "sign"."role": 1."enabled": false
}, {
"id": "9"."name": "hystrix"."role": 0."enabled": false}}]Copy the code
Combined with the key function of route matching, it seems that its role is to determine whether to enable and obtain the selector by name
public abstract class AbstractSoulPlugin implements SoulPlugin {
@Override
public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
String pluginName = named();
final PluginData pluginData = BaseDataCache.getInstance().obtainPluginData(pluginName);
// Used to check whether the function is enabled
if(pluginData ! =null&& pluginData.getEnabled()) { ....... }}}Copy the code
The process of plug-in data is easier to locate, after all, the previous process is also combed almost
The initialization process for selector and rule data is not addressed here, but the logic is basically the same as for plug-in data
conclusion
Today we looked at initialization of plug-ins and related configurations
The initialization of the plug-in is implemented using Spring’s automatic configuration loading mechanism, and the plug-in needs to introduce any start dependencies
Plugin data, selector data, rule data initialization, all from the Websocket (or synchronous communication module) from the side, after receiving data, configuration load to the local
We will also see some delete and update related operations, which will fall under the data synchronization analysis later
Soul Gateway source code analysis article list
Github
-
Soul source reading (a) overview
-
Soul source code reading (two) the initial run of the code
-
HTTP request processing overview
-
Dubbo Request Overview
-
Soul Gateway source code reading (five) request type exploration
-
Soul Gateway source code reading (6) Sofa request processing overview
-
Soul gateway source code reading (seven) a stream limit plug-in exploration
-
Soul gateway source code reading (eight) route matching exploration
-
HTTP parameter request error
The Denver nuggets
-
Soul Gateway source code read (a) overview
-
Soul Gateway source code reading (two) the initial operation of the code
-
Soul Gateway source code reading (3) Request processing overview
-
Dubbo Request Overview
-
Soul Gateway source code reading (five) request type exploration
-
Soul Gateway source code reading (6) Sofa request processing overview
-
Soul gateway source code reading (seven) a stream limit plug-in exploration
-
Soul gateway source code reading (eight) route matching exploration
-
HTTP parameter request error