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