These days leave at home, just nothing can be crazy output, originally wanted to write DUBBO source code analysis, but found that write DUBBO source code too much, so find a write not so much framework, so choose SOFARPC this framework.

SOFARPC is an open source RPC framework of Ant Financial. Compared with DUBBO, SOFARPC does not have so much historical burden, and its code is more concise, its design ideas are clearer, and it is easier to understand the code.

So why rewrite the native SPI? The official explanation is as follows:

  1. According to the need to load
  2. You can have aliases
  3. You can have priorities for sorting and overwriting
  4. You can control whether it is a singleton
  5. You can use encoding in certain scenarios
  6. You can specify the extension configuration location
  7. Other extension points can be excluded

The whole process is as follows:

Take ConsumerBootstrap as an example:

Start with an abstract class:

@Extensible(singleton = false)
public abstract class ConsumerBootstrap<T> {... }Copy the code

Specify the extension implementation class:

@Extension("sofa")
public class DefaultConsumerBootstrap<T> extends ConsumerBootstrap<T> {... }Copy the code

Extension description file meta-inf/services/sofa – RPC/com. Alipay. Sofa. RPC. The bootstrap. ConsumerBootstrap

sofa=com.alipay.sofa.rpc.bootstrap.DefaultConsumerBootstrap
Copy the code

When these preparations are complete, call them directly.

ConsumerBootstrap sofa =  ExtensionLoaderFactory.getExtensionLoader(ConsumerBootstrap.class).getExtension("sofa");
Copy the code

Let’s take a look at the source code for ExtensionLoaderFactory

    /** * All extension loader {Class: ExtensionLoader} *
    private static final ConcurrentMap<Class, ExtensionLoader> LOADER_MAP = new ConcurrentHashMap<Class, ExtensionLoader>();


    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> clazz, ExtensionLoaderListener<T> listener) {
        ExtensionLoader<T> loader = LOADER_MAP.get(clazz);
        if (loader == null) {
            // if you can't get it, lock it
            synchronized (ExtensionLoaderFactory.class) {
                // Prevent other threads from operating on get again
                loader = LOADER_MAP.get(clazz);
                if (loader == null) {
                    loader = newExtensionLoader<T>(clazz, listener); LOADER_MAP.put(clazz, loader); }}}return loader;
    }
Copy the code

Then we look at the constructor of the ExtensionLoader class

    protected ExtensionLoader(Class<T> interfaceClass, boolean autoLoad, ExtensionLoaderListener<T> listener) {
        // If closing is being performed, the property is empty and returned directly
        if (RpcRunningState.isShuttingDown()) {
            this.interfaceClass = null;
            this.interfaceName = null;
            this.listener = null;
            this.factory = null;
            this.extensible = null;
            this.all = null;
            return;
        }
        // The interface is empty, neither an interface nor an abstract class
        if (interfaceClass == null| |! (interfaceClass.isInterface() || Modifier.isAbstract(interfaceClass.getModifiers()))) {throw new IllegalArgumentException("Extensible class must be interface or abstract class!");
        }
        // The name of the currently loaded interface class
        this.interfaceClass = interfaceClass;
        // Interface name
        this.interfaceName = ClassTypeUtils.getTypeStr(interfaceClass);
        this.listener = listener;
        // Interface must have Extensible annotations
        Extensible extensible = interfaceClass.getAnnotation(Extensible.class);
        if (extensible == null) {
            throw new IllegalArgumentException(
                    "Error when load extensible interface " + interfaceName + ", must add annotation @Extensible.");
        } else {
            this.extensible = extensible;
        }
        // If it is a singleton, then factory is not null
        this.factory = extensible.singleton() ? new ConcurrentHashMap<String, T>() : null;
        // This property contains all the implementation classes of this interface
        this.all = new ConcurrentHashMap<String, ExtensionClass<T>>();
        if (autoLoad) {
            // Get the path to the extension point load
            List<String> paths = RpcConfigs.getListValue(RpcOptions.EXTENSION_LOAD_PATH);
            for (String path : paths) {
                // Load the file according to the pathloadFromFile(path); }}}Copy the code

Get all the extension point loading paths and go to loadFromFile to load the file

    protected synchronized void loadFromFile(String path) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Loading extension of extensible {} from path: {}", interfaceName, path);
        }
        // By default, if the file name is not specified, the interface name is used
        String file = StringUtils.isBlank(extensible.file()) ? interfaceName : extensible.file().trim();
        String fullFileName = path + file;
        try {
            ClassLoader classLoader = ClassLoaderUtils.getClassLoader(getClass());
            loadFromClassLoader(classLoader, fullFileName);
        } catch (Throwable t) {
            if (LOGGER.isErrorEnabled()) {
                LOGGER.error("Failed to load extension of extensible " + interfaceName + " from path:"+ fullFileName, t); }}}protected void loadFromClassLoader(ClassLoader classLoader, String fullFileName) throws Throwable { Enumeration<URL> urls = classLoader ! =null ? classLoader.getResources(fullFileName)
            : ClassLoader.getSystemResources(fullFileName);
        // Multiple files may exist.
        if(urls ! =null) {
            while (urls.hasMoreElements()) {
                // Read a file
                URL url = urls.nextElement();
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Loading extension of extensible {} from classloader: {} and file: {}",
                        interfaceName, classLoader, url);
                }
                BufferedReader reader = null;
                try {
                    reader = new BufferedReader(new InputStreamReader(url.openStream(), "UTF-8"));
                    String line;
                    while((line = reader.readLine()) ! =null) { readLine(url, line); }}catch (Throwable t) {
                    if (LOGGER.isWarnEnabled()) {
                        LOGGER.warn("Failed to load extension of extensible " + interfaceName
                            + " from classloader: " + classLoader + " and file:"+ url, t); }}finally {
                    if(reader ! =null) {
                        reader.close();
                    }
                }
            }
        }
    }
Copy the code

ReadLine reads every line in the prop file, loads the implementation class file, and then adds the file to the All property

    protected void readLine(URL url, String line) {
        // Read a line in the file and split the line with a = sign
        String[] aliasAndClassName = parseAliasAndClassName(line);
        if (aliasAndClassName == null|| aliasAndClassName.length ! =2) {
            return;
        }
        / / alias
        String alias = aliasAndClassName[0];
        / / package name
        String className = aliasAndClassName[1];
        // Read the implementation class of the configuration
        Class tmp;
        try {
            tmp = ClassUtils.forName(className, false);
        } catch (Throwable e) {
            if (LOGGER.isWarnEnabled()) {
                LOGGER.warn("Extension {} of extensible {} is disabled, cause by: {}",
                    className, interfaceName, ExceptionUtils.toShortString(e, 2));
            }
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Extension " + className + " of extensible " + interfaceName + " is disabled.", e);
            }
            return;
        }
        if(! interfaceClass.isAssignableFrom(tmp)) {throw new IllegalArgumentException("Error when load extension of extensible " + interfaceName +
                " from file:" + url + "," + className + " is not subtype of interface.");
        }
        Class<? extends T> implClass = (Class<? extends T>) tmp;

        // Check if there is an extensible identifier
        Extension extension = implClass.getAnnotation(Extension.class);
        if (extension == null) {
            throw new IllegalArgumentException("Error when load extension of extensible " + interfaceName +
                " from file:" + url + "," + className + " must add annotation @Extension.");
        } else {
            String aliasInCode = extension.value();
            if (StringUtils.isBlank(aliasInCode)) {
                // The Extension implementation class is not configured with the @extension tag
                throw new IllegalArgumentException("Error when load extension of extensible " + interfaceClass +
                    " from file:" + url + "," + className + "'s alias of @Extension is blank");
            }
            if (alias == null) {
                // spi file is not configured, use code
                alias = aliasInCode;
            } else {
                // spi file configuration is inconsistent with the code
                if(! aliasInCode.equals(alias)) {throw new IllegalArgumentException("Error when load extension of extensible " + interfaceName +
                        " from file:" + url + ", aliases of " + className + " are " +
                        "not equal between " + aliasInCode + "(code) and " + alias + "(file)."); }}// Interface number is required, implementation class is not set
            if (extensible.coded() && extension.code() < 0) {
                throw new IllegalArgumentException("Error when load extension of extensible " + interfaceName +
                    " from file:" + url + ", code of @Extension must >=0 at " + className + "."); }}// Cannot be default or *
        if (StringUtils.DEFAULT.equals(alias) || StringUtils.ALL.equals(alias)) {
            throw new IllegalArgumentException("Error when load extension of extensible " + interfaceName +
                " from file:" + url + ", alias of @Extension must not \"default\" and \"*\" at " + className + ".");
        }
        // Check if there are any names with the same name
        ExtensionClass old = all.get(alias);
        ExtensionClass<T> extensionClass = null;
        if(old ! =null) {
            // If the current extension can overwrite another extension with the same name
            if (extension.override()) {
                // If the priority is not as high as the old one, ignore it
                if (extension.order() < old.getOrder()) {
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug("Extension of extensible {} with alias {} override from {} to {} failure, " +
                            "cause by: order of old extension is higher", interfaceName, alias, old.getClazz(), implClass); }}else {
                    if (LOGGER.isInfoEnabled()) {
                        LOGGER.info("Extension of extensible {} with alias {}: {} has been override to {}",
                            interfaceName, alias, old.getClazz(), implClass);
                    }
                    // If the current extension can overwrite another extension with the same nameextensionClass = buildClass(extension, implClass, alias); }}// If the old extension is overwritable
            else {
                if (old.isOverride() && old.getOrder() >= extension.order()) {
                    // If the override extension is already loaded, load it to the original extension
                    if (LOGGER.isInfoEnabled()) {
                        LOGGER.info("Extension of extensible {} with alias {}: {} has been loaded, ignore origin {}", interfaceName, alias, old.getClazz(), implClass); }}else {
                    // If it cannot be overridden, an existing exception is thrown
                    throw new IllegalStateException(
                        "Error when load extension of extensible " + interfaceClass + " from file:" + url +
                            ", Duplicate class with same alias: " + alias + "," + old.getClazz() + " and "+ implClass); }}}else {
            extensionClass = buildClass(extension, implClass, alias);
        }
        if(extensionClass ! =null) {
            // Check whether there are mutually exclusive extension points
            for (Map.Entry<String, ExtensionClass<T>> entry : all.entrySet()) {
                ExtensionClass existed = entry.getValue();
                if (extensionClass.getOrder() >= existed.getOrder()) {
                    // New priority >= old priority, check whether the new extension excludes the old extension
                    String[] rejection = extensionClass.getRejection();
                    if (CommonUtils.isNotEmpty(rejection)) {
                        for (String rej : rejection) {
                            existed = all.get(rej);
                            if (existed == null || extensionClass.getOrder() < existed.getOrder()) {
                                continue;
                            }
                            ExtensionClass removed = all.remove(rej);
                            if(removed ! =null) {
                                if (LOGGER.isInfoEnabled()) {
                                    LOGGER.info(
                                        "Extension of extensible {} with alias {}: {} has been reject by new {}",
                                        interfaceName, removed.getAlias(), removed.getClazz(), implClass);
                                }
                            }
                        }
                    }
                } else {
                    String[] rejection = existed.getRejection();
                    if (CommonUtils.isNotEmpty(rejection)) {
                        for (String rej : rejection) {
                            if (rej.equals(extensionClass.getAlias())) {
                                // is excluded by other extensions
                                if (LOGGER.isInfoEnabled()) {
                                    LOGGER.info(
                                        "Extension of extensible {} with alias {}: {} has been reject by old {}",
                                        interfaceName, alias, implClass, existed.getClazz());
                                    return; } } } } } } loadSuccess(alias, extensionClass); }}Copy the code

We’ll go back to that after we load the file

ConsumerBootstrap sofa =  ExtensionLoaderFactory.getExtensionLoader(ConsumerBootstrap.class).getExtension("sofa");
Copy the code

Go into the getExtension method

    public ExtensionClass<T> getExtensionClass(String alias) {
        return all == null ? null : all.get(alias);
    }

    public T getExtension(String alias) {
        // Get the loaded class from the all attribute
        ExtensionClass<T> extensionClass = getExtensionClass(alias);
        if (extensionClass == null) {
            throw new SofaRpcRuntimeException("Not found extension of " + interfaceName + " named: \"" + alias + "\"!");
        } else {
            // If the class is a singleton, factory is not null
            if(extensible.singleton() && factory ! =null) {
                T t = factory.get(alias);
                if (t == null) {
                    synchronized (this) {
                        t = factory.get(alias);
                        if (t == null) {
                            / / instantiate
                            t = extensionClass.getExtInstance();
                            // Add the singleton class to the factoryfactory.put(alias, t); }}}return t;
            } else {
                / / instantiate
                returnextensionClass.getExtInstance(); }}}Copy the code

Let’s go to ExtensionClass and look at the getExtInstance method

    /** * Server instance objects (reserved only for singletons) * volatile to ensure visibility */
    private volatile transient T       instance;
    
    /** * returns the server instance object if it is a singleton, or the newly created instance object ** if it is not@paramArgTypes constructor argument type *@paramThe args constructor argument is *@returnExtension point object instance ext Instance */
    public T getExtInstance(Class[] argTypes, Object[] args) {
        if(clazz ! =null) {
            try {
                if (singleton) { // If it is a singleton
                    if (instance == null) {
                        synchronized (this) {
                            if (instance == null) {
                                // Create an instance through reflectioninstance = ClassUtils.newInstanceWithArgs(clazz, argTypes, args); }}}return instance; // Keep the singleton
                } else {
                    // Create an instance through reflection
                    returnClassUtils.newInstanceWithArgs(clazz, argTypes, args); }}catch (Exception e) {
                throw new SofaRpcRuntimeException("create " + clazz.getCanonicalName() + " instance error", e); }}throw new SofaRpcRuntimeException("Class of ExtensionClass is null");
    }
Copy the code

After reading SOFARPC’s extension class implementation, I feel that the code is written very clean, the logic is very clear, there are a lot of places to learn, such as thread safety using double-checked locking and volatile to ensure visibility.

The original link: www.cnblogs.com/luozhiyun/p…