SPI plug-in extension mechanism Motan series of -Motan

Open source is recommended

Recommend a one-stop performance monitoring tools (open source project)

Pepper-Metrics is developed with a colleague with open source components, main function is relatively lightweight way of popular open source components (jedis / mybatis / motan / dubbo / servlet) integration, collect and calculate metrics, and supports output to a log and converted into a variety of sequence databases compatible data format, supporting grafana dashboard friendly on display. Among the principle of complete project documentation, and all SPI-based scalable architecture designed to facilitate the development of new plug-ins. There is also a separate demo project based docker-compose can quickly start a demo to see examples of the effect github.com/zrbcool/pep... . If you find it useful, then, to trouble a star, also welcome to participate in the development, thank you :)


Into the title...

The SPI 0 Motan

Motan of SPI and the SPI Dubbo similar, it is optimized on the basis of native Java SPI thinking on, and very similar to the use of Java native SPI.

Introduction Java native SPI has a lot of related articles, not repeat them here. Here mainly introduce the SPI mechanism in Motan, starting with the use of talking.

0.1 SPI usage

Here in order to achieve a Filter extensions, for example, talk about how he used.

Filter action is to Providerend, or receiving a request Consumerto intercept When sending a request to do something special. Here from Consumerthe perspective of the end, to make a statistical calculation of a single request response time requirements.

First you need to write a class that implements com.weibo.api.motan.filter.Filterthe interface filtermethod.

@SpiMeta(name = "profiler")
public class ProfilerFilter implements Filter {
    
    @Override
    public Response filter(Caller<?> caller, Request request) {
        // 记录开始时间
        long begin = System.nanoTime();
        try {
            final Response response = caller.call(request);
            return response;
        } finally {
            // 打印本次响应时间
            System.out.println("Time cost : " + (System.nanoTime() - begin));
        }
    }
}
复制代码

Second, META-INF/servicescreate a directory named com.weibo.api.motan.filter.Filterin the document, as follows:

# 例如:com.pepper.metrics.integration.motan.MotanProfilerFilter
#全限定名称#.MotanProfilerFilter
复制代码

Then to the Protocol Configuration filter

ProtocolConfigBean config = new ProtocolConfigBean();
config.setName("motan");
config.setMaxContentLength(1048576);
config.setFilter("profiler"); // 配置filter
return config;
复制代码

Finally, RefererConfigthe use of this ProtocolConfigcan be.

BasicRefererConfigBean config = new BasicRefererConfigBean();
config.setProtocol("demoMotan");
// ... 省略其他配置 ...

复制代码

Thus, at Consumereach end can intercept the request and the response time of printing.

Next, we continue to study how Motan is to do this too.

1 SPI management

Motan achieve the SPI in motan-core/com/weibo/api/motan/core/extensionthe. Organizational structure is as follows:

motan-core/com.weibo.api.motan.core.extension
    |-Activation:SPI的扩展功能,例如过滤、排序
    |-ActivationComparator:排序比较器
    |-ExtensionLoader:核心,主要负责SPI的扫描和加载
    |-Scope:模式枚举,单例、多例
    |-Spi:注解,作用在接口上,表明这个接口的实现可以通过SPI的形式加载
    |-SpiMeta:注解,作用在具体的SPI接口的实现类上,标注该扩展的名称

复制代码

1.1 Management of internal data structures

private static ConcurrentMap<Class<?>, ExtensionLoader<?>> extensionLoaders = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
private ConcurrentMap<String, T> singletonInstances = null;
private ConcurrentMap<String, Class<T>> extensionClasses = null;
private Class<T> type;
private ClassLoader classLoader; // 类加载使用的ClassLoader
复制代码

extensionLoadersIs a class variable, he managed by @Spiits annotation label interface ExtensionLoadermapping, all SPI as global manager.

singletonInstancesMaintenance of the current ExtensionLoadersingleton extension.

extensionClassesMaintenance of the current ExtensionLoaderall instances of extended Class object is used to create multiple cases (created by class.newInstance).

typeMaintenance of the current @Spiannotation annotation interface classobjects.

1.2 ExtensionLoader initialization

In Motanmay be initialized ExtensionLoader (Filter SPI described above as an example) in the following ways:

// 初始化 Filter 到全局管理器 `extensionLoaders` 中
ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(Filter.class);

复制代码

Then we look at the specific ExtensionLoader.getExtensionLoader(Filter.class)'ve done what.

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    // 检查type是否为空、type是否是接口类型、type是否被@Spi标注,检查失败会抛出异常
    checkInterfaceType(type);
    // 尝试从上文提到的 `extensionLoaders` 管理器中获取已有的ExtensionLoader
    ExtensionLoader<T> loader = (ExtensionLoader<T>) extensionLoaders.get(type);

    // 获取失败的话,尝试扫描并加载指定type的扩展,并初始化之
    if (loader == null) {
        loader = initExtensionLoader(type);
    }
    return loader;
}
复制代码

Then take a look at initExtensionLoadermethods did what.

// synchronized锁控制,防止并发初始化
public static synchronized <T> ExtensionLoader<T> initExtensionLoader(Class<T> type) {
    ExtensionLoader<T> loader = (ExtensionLoader<T>) extensionLoaders.get(type);
    // 二层检查,防止并发问题
    if (loader == null) {
        // type会赋值给实例变量 `type`,并初始化实例变量 `classLoader` 为当前线程上线文的ClassLoader
        loader = new ExtensionLoader<T>(type);
        // 添加到全局管理器 `extensionLoaders` 中
        extensionLoaders.putIfAbsent(type, loader);

        loader = (ExtensionLoader<T>) extensionLoaders.get(type);
    }

    return loader;
}
复制代码

At this point, we initialize the Filterinterface ExtensionLoader, and hosting it to the extensionLoadersmiddle.

And scanning and acquiring loading 1.3 SPI instance specified extension

Motan It is a lazy loading strategy, when you first get specific instance of a certain extension, will scan and load all of the extended example.

For example, you can get the name we created above by way profilerof Filterextension instance of an interface.

// 初始化 Filter 到全局管理器 `extensionLoaders` 中
ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(Filter.class);
Filter profilterFilter = extensionLoader.getExtension("profiler");
复制代码

FilterThe interface ExtensionLoaderis in fact the first time extensionLoader.getExtension("profiler")completed while. The following look at the specific getExtensionmethod did what.

public T getExtension(String name) {
    // Notes:就是通过这个checkInit方法扫描和加载的
    checkInit();
    // .. 暂时省略 ..
}
复制代码

Continue to study checkInitmethods.

private volatile boolean init = false;
private void checkInit() {
    // 用init标记,只初始化一次
    if (!init) {
        loadExtensionClasses();
    }
}

private static final String PREFIX = "META-INF/services/";
private synchronized void loadExtensionClasses() {
    if (init) {
        return;
    }
    // 扫描和加载
    extensionClasses = loadExtensionClasses(PREFIX);
    singletonInstances = new ConcurrentHashMap<String, T>();

    init = true;
}
复制代码

loadExtensionClassesThe method will scan META-INF/services/all files, and parse the contents of the file, it calls the loadClassmethod, the method is implemented as follows:

// classNames 就是每个文件中的具体扩展实现的全限定名称。
private ConcurrentMap<String, Class<T>> loadClass(List<String> classNames) {
    ConcurrentMap<String, Class<T>> map = new ConcurrentHashMap<String, Class<T>>();

    for (String className : classNames) {
        try {
            Class<T> clz;
            if (classLoader == null) {
                clz = (Class<T>) Class.forName(className);
            } else {
                clz = (Class<T>) Class.forName(className, true, classLoader);
            }

            checkExtensionType(clz);
            // 获取 @SpiMeta 注解声明的名称
            String spiName = getSpiName(clz);

            if (map.containsKey(spiName)) {
                failThrows(clz, ":Error spiName already exist " + spiName);
            } else {
                map.put(spiName, clz);
            }
        } catch (Exception e) {
            failLog(type, "Error load spi class", e);
        }
    }

    return map;
}
复制代码

The way to do things is to get all legitimate extension class. The final return value will be assigned to the instance variables extensionClasses, thereby completing the scanning and loading work.

PS: From the above, extensionClasses is @SpiMeta the KV mapping class names and corresponding extensions specific implementation. Above text ProfilerFilter, for example, it, KEY = profiler, VALUE = ProfilerFilter.class

1.4 SPI for specific extension implementation

Continue to look just getExtensionpart of the process will be omitted.

public T getExtension(String name) {
    checkInit();

    if (name == null) {
        return null;
    }

    try {
        Spi spi = type.getAnnotation(Spi.class);

        // 获取单例
        if (spi.scope() == Scope.SINGLETON) {
            return getSingletonInstance(name);
        } else {
            // 获取多例
            Class<T> clz = extensionClasses.get(name);

            if (clz == null) {
                return null;
            }

            return clz.newInstance();
        }
    } catch (Exception e) {
        failThrows(type, "Error when getExtension " + name, e);
    }

    return null;
}
复制代码

Acquiring a plurality of Example situation can easily directly be loaded from the front of good extensionClassesacquisition, if it acquires newInstance()a new instance.

The following example of the case of single look: getSingletonInstanceMethod.

private T getSingletonInstance(String name) throws InstantiationException, IllegalAccessException {
    T obj = singletonInstances.get(name);

    if (obj != null) {
        return obj;
    }

    Class<T> clz = extensionClasses.get(name);

    if (clz == null) {
        return null;
    }

    synchronized (singletonInstances) {
        obj = singletonInstances.get(name);
        if (obj != null) {
            return obj;
        }

        obj = clz.newInstance();
        singletonInstances.put(name, obj);
    }

    return obj;
}
复制代码

Acquiring a single embodiment, it will attempt to preferentially from a single set of embodiments singletonInstancesacquiring, if not acquired, not described in this example was added to a single embodiment (specific example or no corresponding name) singleton set, and then attempts from extensionClassesacquired in If you still can not get, is really gone, if acquired, will be a new singleton instance to the collection.

These are the Motan specific acquisition mode SPI expansion to achieve.

The above is the Motan SPI mechanism.


I welcome attention to the micro-channel public number

No public

Guess you like

Origin juejin.im/post/5d7f26a06fb9a06b2a2070df