dubbo source --SPI extension mechanism

The best there is AOP, IOC, MVC framework and basis basis dubbo use and re-read Oh.

What is SPI

spi full name of the Service Provider Interface, Service Provider Interface, is a set of Java used to be provided by third-party implementation or expansion of API.

JDK SPI is not used may be an example of its own run under Baidu, talk source here.

SPI is the core idea of ​​decoupling based interface, a policy model, configuration enables dynamic expansion of the implementation class.

Experienced developers certainly used a lot Driverof products to achieve, such as oracle.jdbc.driver.OracleDriverand oracle.jdbc.OracleDriver, as well as ODBC (Microsoft connection that database) to JDBC drivers, for example, we analyze how the dynamic expansion of the JDK is done:

In the mysql-connector-java-5.1.44.jar!/META-INF/services/java.sql.Driverfile:

com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver
复制代码

Both are java.sql.Driverfully qualified name of the implementation class, where we look at this load Driver.class:

    private static void loadInitialDrivers() {
        // 先从JVM启动参数里面获取到jdbc.drivers的值 (-Djdbc.drivers=xxx)
        String drivers;
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run() {
                    return System.getProperty("jdbc.drivers");
                }
            });
        } catch (Exception ex) {
            drivers = null;
        }

        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                // JDK提供的加载SPI方式:ServiceLoader
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
                try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });

        println("DriverManager.initialize: jdbc.drivers = " + drivers);

        if (drivers == null || drivers.equals("")) {
            return;
        }
        String[] driversList = drivers.split(":");
        println("number of Drivers:" + driversList.length);
        for (String aDriver : driversList) {
            try {
                println("DriverManager.Initialize: loading " + aDriver);
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                println("DriverManager.Initialize: load failed: " + ex);
            }
        }
    }
复制代码

Let me explain about the process:

  1. Load JVM startup properties, get driverName
  2. Driver loaded with all the implementation class ServiceLoader
  3. According jvm properties driver name to load the class: Class.forName (driverName)

The first step and the third part we should be very familiar with. To explore the main source of under ServiceLoader:

#首先它实现了迭代类
public final class ServiceLoader<S> implements Iterable<S>
所以从该对象中拿到的迭代器, 是定制的迭代器,我们再调用迭代器的hasNext方法,事实上调用的是:

        public boolean hasNext() {
            if (acc == null) {
                return hasNextService();
            } else {
                PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                    public Boolean run() { return hasNextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }
        private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    // service就是我们传进来的接口类对象
                    // PREFIX 是常量: private static final String PREFIX = "META-INF/services/";
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                // 这里面会打开流去解析每一行, 把实现类全限定名加到names列表并返回其迭代器对象,用pending来接收
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }
复制代码

Thus, we can reverse the launch of SPI JDK to use:

  1. SPI profile path: META-INF/services/not change.
  2. Profile name to interface fully qualified class name declaration
  3. Profiles implemented in each row is a fully qualified class name of the class

Java SPI problem

Dubbo Why not choose their own java-SPI and then set it to rewrite.

  1. JDK standard SPI will be a one-time extension point to instantiate all realize, if extension initialization achieve very time-consuming, but also if the load on useless, would be a waste of resources.
  2. If the extension fails to load point, even the name of the extension fail to get the point.
  3. It does not support the AOP and IOC.

Dubbo SPI

Dubbo strengthen the SPI expansion comes from the JDK standard SPI (Service Provider Interface) discovery mechanism for expansion. Improvement of the above problems SPI standard JDK.

As used herein do not say, I have not used the students can go to the official look: dubbo.apache.org/zh-cn/docs/...

    public static void main(String[] args) {
        ExtensionLoader<Color> loader = ExtensionLoader.getExtensionLoader(Color.class);
        Color yellow = loader.getExtension("yellow");
        System.out.println(yellow.getColor());
    }
复制代码

This is a test Dubbo SPI piece of code, you can see getExtensionLoadera dubbo SPI entrance, we look at how dubbo is a dynamic extension.

Color yellow = loader.getExtension("yellow");In this line of code, through the instantiation of the class from the configuration process of obtaining an extension to the class name, then we see from the getExtension method (similar spirng of getBean ()).

    public T getExtension(String name) {
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        }
        if ("true".equals(name)) {
            return getDefaultExtension();
        }
        // 根据接口名获取它的持有类, 如果有从缓存中取, 没有就重新new
        final Holder<Object> holder = getOrCreateHolder(name);
        // 获取到实例
        Object instance = holder.get();
        // 双重检锁 获取和创建实例
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    instance = createExtension(name);
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }
复制代码

Suppose now that nothing will to createExtension (name); method, this method creates an instance of the extended class.

    private T createExtension(String name) {
      // 根据接口名获取所有实现类
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw findException(name);
        }
        try {
            // 从缓存中获取实例
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            // 缓存(在时候就加载了)中没有, 调用反射创建
            if (instance == null) {
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            // inject 依赖注入, 给实例注入需要的依赖对象
            injectExtension(instance);
            // 从缓存拿接口的包装类(也是实现类)
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            // 如果包装类不是空, 就遍历通过构造方法反射创建实例
            if (CollectionUtils.isNotEmpty(wrapperClasses)) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                    type + ") couldn't be instantiated: " + t.getMessage(), t);
        }
    }
复制代码

createExtension method comprising the steps of:

  1. By getExtensionClassesacquiring all of the implementation class
  2. By reflecting clazz.newInstance()create extended objects
  3. injectExtension(instance);Injecting dependencies to expand object
  4. Examples of the method of configuration parameters with the interface, the object is wrapped in a respective expanding object Wrapper

From wrapperClass.getConstructor(type).newInstance(instance);looking at the code in, Wrapper must be the object constructor parameter interface class object.

How to get that all extensions like it? We explore the getExtensionClassesmethod:

    private Map<String, Class<?>> getExtensionClasses() {
        // 从缓存中拿map, 如果没有,就调用loadExtensionClasses
        Map<String, Class<?>> classes = cachedClasses.get();
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }
复制代码

This method is very simple, take the map from the cache, and if not, call loadExtensionClasses, look loadExtensionClasses method to do what:

    private Map<String, Class<?>> loadExtensionClasses() {
        cacheDefaultExtensionName();

        Map<String, Class<?>> extensionClasses = new HashMap<>();
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        return extensionClasses;
    }
复制代码
  1. Call cacheDefaultExtensionName get the default implementation class
  2. Call loadDirectory find extension class configuration file from several files

Let's look at how to get cacheDefaultExtensionName is the default implementation class:

    private void cacheDefaultExtensionName() {
        // 获取SPI上的注解, 如果有设置value值,则取出, 这个值是默认实现类噢
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if (defaultAnnotation != null) {
            String value = defaultAnnotation.value();
            if ((value = value.trim()).length() > 0) {
                String[] names = NAME_SEPARATOR.split(value);
                if (names.length > 1) {
                    throw new IllegalStateException("More than 1 default extension name on extension " + type.getName()
                            + ": " + Arrays.toString(names));
                }
                if (names.length == 1) {
                    cachedDefaultName = names[0];
                }
            }
        }
    }
复制代码

This method is very simple, is to take out the SPI value annotation to the corresponding default implementation.

    private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
        String fileName = dir + type;
        try {
            Enumeration<java.net.URL> urls;
            // 阿里封装的获取 classLoader 的方法
            ClassLoader classLoader = findClassLoader();
            if (classLoader != null) {
                // jdk的方法, 与java spi一样的噢
                urls = classLoader.getResources(fileName);
            } else {
                urls = ClassLoader.getSystemResources(fileName);
            }
            // 拿到url后遍历调用loadResource方法
            if (urls != null) {
                while (urls.hasMoreElements()) {
                    java.net.URL resourceURL = urls.nextElement();
                    loadResource(extensionClasses, classLoader, resourceURL);
                }
            }
        } catch (Throwable t) {
            logger.error("Exception occurred when loading extension class (interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
    }
复制代码

More specific you go yourself, I describe what has been done to the next:

  1. Get a url to the file stream
  2. Parsing each line of the =front of the assigned number is name, assigned to the latter class of fully qualified path implementation
  3. Examples of the implementation classes reflecting each row, and call assignment to achieve a final loadClass method extensionClasses (which new objects of a map until only later extensionClasses assignment).
    private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
        if (!type.isAssignableFrom(clazz)) {
            throw new IllegalStateException("Error occurred when loading extension class (interface: " +
                    type + ", class line: " + clazz.getName() + "), class "
                    + clazz.getName() + " is not subtype of interface.");
        }
        // 如果是Adaptive注解的类, 就把它放到cacheAdaptiveClass缓存中
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            cacheAdaptiveClass(clazz);
        } else if (isWrapperClass(clazz)) {
            cacheWrapperClass(clazz);
        } else {
            clazz.getConstructor();
            if (StringUtils.isEmpty(name)) {
                name = findAnnotationName(clazz);
                if (name.length() == 0) {
                    throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
                }
            }
            // 分割name, 因为配置文件中name可以有很多个,然后每个name对应赋值一个实现类对象(即使对象都相同)
            String[] names = NAME_SEPARATOR.split(name);
            if (ArrayUtils.isNotEmpty(names)) {
                cacheActivateClass(clazz, names[0]);
                for (String n : names) {
                    cacheName(clazz, n);
                    saveInExtensionClass(extensionClasses, clazz, n);
                }
            }
        }
    }
复制代码
  1. Adaptive determines whether annotated classes like, put it in the cache and the like corresponding to cacheAdaptiveClass
  2. Division name, file name because the configuration can be many, then each assigned a name corresponding to the class object implement (even if the objects are the same)

Well, here the extension class loading is complete, next to its dependency on injection (injectExtension Method):

    private T injectExtension(T instance) {
        try {
            // 你一定很好奇,如果objectFactory是空怎么办,其实再
            if (objectFactory != null) { 
                // 遍历实例所有的方法
                for (Method method : instance.getClass().getMethods()) {
                    // 如果是setter方法
                    if (isSetter(method)) {
                        /**
                         * Check {@link DisableInject} to see if we need auto injection for this property
                         */
                        if (method.getAnnotation(DisableInject.class) != null) {
                            continue;
                        }
                        Class<?> pt = method.getParameterTypes()[0];
                        if (ReflectUtils.isPrimitives(pt)) {
                            continue;
                        }
                        try {
                            // 获取setter方法属性
                            String property = getSetterProperty(method);
                            // 根据第一个参数和类型 从List<ExtensionFactory>中获取实例
                            Object object = objectFactory.getExtension(pt, property);
                            if (object != null) {
                                // 利用反射将获取的依赖注入到当前实例中
                                method.invoke(instance, object);
                            }
                        } catch (Exception e) {
                            logger.error("Failed to inject via method " + method.getName()
                                    + " of interface " + type.getName() + ": " + e.getMessage(), e);
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return instance;
    }
复制代码
  1. If objectFactory is empty (no proof to load the extension class), the first class to load the extension of IOC dubbo.
  2. Acquisition parameters and type setter methods, and to get the object from dubbo instance dependency of the IOC.
  3. Reflecting the current injected into the dependent object instance.

Why must objectFactory not empty it, because then call ExtensionLoader.getExtensionLoader(Color.class);when the method, dubbo have to extend the class to initialize the IOC, see details below.

In fact, when you call getExtensionLoader, put objectFactory instantiate, and initialize the IOC:

    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        ...
        if (loader == null) {
             // 在这里new的对象
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }
    
    private ExtensionLoader(Class<?> type) {
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }
复制代码

It has called these methods:

  1. getAdaptiveExtension (taken from the cache, it does not call the next method)
  2. createAdaptiveExtension (taken from the cache, it does not call the next method)
  3. 4 getAdaptiveExtensionClass method call, then call method 5
  4. getExtensionClasses this approach and on the front ( loader.getExtension("yellow");) of the same.
  5. createAdaptiveExtensionClass method using bytecode to generate the proxy javassist default, then the Compiler for analyzing dubbo into classes and returns.

Unconsciously, dubbo of SPI also has its own unique AOP and IOC will read the source code.

You might wonder, I do not see AOP ah.

Remember that a Wrapper resolve it, dubbo a constructor to instantiate Wrapper then dependency injection, AOP to complete a similar function, which you can then customize your logic Wrapper.

Because of space long enough, javassist part heavy enough to stand out in terms of a, if you read was perplexed, then read it again, read the source code, the most important is the active receiver mentality. Multi-commissioning a few times, come on!

Welcome to public concern No.

GitHub Address: github.com/fantj2016/j...

Guess you like

Origin juejin.im/post/5e50ffaa6fb9a07cbe34643f