What problem is the JDK’s SPI designed to solve? How do you do that? The advantages and disadvantages?

define

A Service Provider Interface is an Interface provided to Service providers and developers who extend the functionality of the framework

Problem solving (quoted on official website)

  • The JDK’s standard SPI instantiates all implementations of extension points at once, which is time-consuming to initialize if there is an extension implementation, but wasteful of resources to load without it.
  • If the extension point fails to load, you will not even get the name of the extension point. Such as: If the RubyScriptEngine class fails to load because the jruby.jar on which RubyScriptEngine depends does not exist, the class fails to load because the jruby.jar on which RubyScriptEngine depends does not exist. This failure cause is eaten up and does not correspond to Ruby. When a user executes a Ruby script, it will report that Ruby is not supported, rather than the actual failure cause.

implementation

Defining service Interfaces

package dictionary.spi;

public interface Dictionary {
    String getDefinition(String word);
}
Copy the code

Implementation of the service provider

package dictionary.spi.impl; import dictionary.spi.Dictionary; import java.util.SortedMap; import java.util.TreeMap; public class ExtendedDictionary implements Dictionary { private SortedMap<String, String> map; /** * Creates a new instance of ExtendedDictionary */ public ExtendedDictionary() { map = new TreeMap<String, String>(); map.put( "xml", "a document standard often used in web services, among other " + "things"); map.put( "REST", "an architecture style for creating, reading, updating, " + "and deleting data that attempts to use the common " + "vocabulary of the HTTP protocol; Representational State " + "Transfer"); } @Override public String getDefinition(String word) { return map.get(word); }}Copy the code

The registration service

In/SRC/main/resources/meta-inf/services/build a dictionary. The spi. The dictionary file file contents dictionary. Spi. Impl. ExtendedDictionaryCopy the code

The caller

package dictionary; import dictionary.spi.Dictionary; import java.util.Iterator; import java.util.ServiceLoader; public class DictionaryDemo { public static void main(String[] args) { ServiceLoader<Dictionary> loader = ServiceLoader.load(Dictionary.class); final Iterator<Dictionary> iterator = loader.iterator(); while (iterator.hasNext()) { final Dictionary next = iterator.next(); System.out.println(next.getDefinition("xml")); System.out.println(next.getDefinition("REST")); }}}Copy the code

Implementation mechanisms

The above example shows that the caller is implementing a call based on the ServiceLoader class. Let’s look at the ServiceLoader source code and write “A simple service-provider loading facility” in the first line of the comment. Let’s look at the load method

public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }
Copy the code

The actual call

private ServiceLoader(Class<S> svc, ClassLoader cl) { service = Objects.requireNonNull(svc, "Service interface cannot be null"); loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; acc = (System.getSecurityManager() ! = null) ? AccessController.getContext() : null; reload(); }Copy the code

The key element in the Reload method is the LazyIterator, which is mainly nextService, with the next section of code implemented through class.forname

try {
      c = Class.forName(cn, false, loader);
 } catch (ClassNotFoundException x) {
      fail(service,"Provider " + cn + " not found");
 }
Copy the code

disadvantages

  • The JDK’s SPI instantiates all implementations of extension points at once, which is time-consuming to initialize if there is an extension implementation, but wasteful of resources to load without it.
  • The JDK SPI does not support default values
  • You use the for loop to determine the object

Why did Dubbo implement another SPI? Does it solve the problem?

Dubbo also implements SPI in response to JDK problems, mainly to solve the following problems

  • Cached objects are supported: SPI’s keys and values are cached in the cachedInstances object, which is a ConcurrentMap
  • Dubbo design default value: @spi (“dubbo”) represents the default SPI object, for example, @spi (“dubbo”) of Protocol is DubboProtocol, Through ExtensionLoader. GetExtensionLoader (Protocol. The class). GetDefaultExtension directly obtained according to the KEY () the default object
  • The design adds AOP functionality in cachedWrapperClasses and wraps XxxxFilterWrapper XxxxListenerWrapper in the original SPI class
  • Dubbo design adds to the IOC by constructor injection, code for: wrapperClass. GetConstructor (type). NewInstance (instance),

Dubbo implements its own extension point mechanism according to the idea of JDK SPI mechanism. ExtensionLoader is the core of Dubbo extension point mechanism, which is equivalent to ServiceLoader in JDK SPI

How to use

 public void test_useAdaptiveClass() throws Exception {
        ExtensionLoader<HasAdaptiveExt> loader = ExtensionLoader.getExtensionLoader(HasAdaptiveExt.class);
        HasAdaptiveExt ext = loader.getAdaptiveExtension();
        assertTrue(ext instanceof HasAdaptiveExt_ManualAdaptive);
    }
Copy the code
/** * @author ding.lid */ @SPI public interface HasAdaptiveExt { @Adaptive String echo(URL url, String s); } public class HasAdaptiveExtImpl1 implements HasAdaptiveExt { public String echo(URL url, String s) { return this.getClass().getSimpleName(); } } @Adaptive public class HasAdaptiveExt_ManualAdaptive implements HasAdaptiveExt { public String echo(URL url, String s) { HasAdaptiveExt addExt1 = ExtensionLoader.getExtensionLoader(HasAdaptiveExt.class).getExtension(url.getParameter("key")); return addExt1.echo(url, s); }}Copy the code

SRC /test/resources/ meta-INF /dubbo/internal There is a file com.alibaba.dubbo.com mon. Extensionloader. The adaptive. HasAdaptiveExt content is as follows

adaptive=com.alibaba.dubbo.common.extensionloader.adaptive.impl.HasAdaptiveExt_ManualAdaptive Impl1=com.alibaba.dubbo.com mon. Extensionloader. The adaptive. Impl. HasAdaptiveExtImpl1 configuration format: configuration name = extension implementation class fully qualified nameCopy the code

From the above example, we can see that:

Pass ExtensionLoader getExtensionLoader loading an interface Find a class that is adaptive to find through getAdaptiveExtension concrete implementation class

  • Meaning of annotations
  1. SPI, the identity of the extension point interface
Extension point declaration configuration file, format change. Examples for Protocol, the configuration file meta-inf/dubbo/com. XXX. The content of the Protocol: XxxProtocol com.foo.YyyProtocol now uses KV format XXX =com.foo.XxxProtocol yyy=com.foo.YyyProtocolCopy the code

Note: If the Extension static field or method signature refers to the tripartite library, class initialization will fail if the tripartite library does not exist, and the Extension flag will not be available, and the exception message will not correspond to the configuration. For example: Extension(” MINA “) failed to load. When the user is configured to use MINA, the Extension point could not be found, instead of loading the Extension point failed and why. (Notes of Dubbo)

  1. @adaptive (methodologically or classically)

Get the extension point used by Dubbo.

  • Automatic injection of associated extension points.
  • Automatic Wrap class for extension points on Wrap.
  • The default extension point obtained is a Adaptive Instance.

We according to ExtensionLoader source code to analyze Dubbo is how to achieve SPI

Take a look at the ExtensionLoader#getExtensionLoader method (static)

/** * <ul> * <li> First check whether the extension point type is empty </li> * <li> Check whether it is an interface </li> * <li> Check whether the SPI annotation is marked </li> * <li> Whether the extension point has already created a loader instance </li> * <li> if the loader instance has not been created, </li> * </ul> * * @param Type Interface type * @param <T> * @return Concrete object */ @suppressWarnings ("unchecked") public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {// check if (type == null) {throw new IllegalArgumentException("Extension type == null"); } if (! type.isInterface()) { throw new IllegalArgumentException("Extension type(" + type + ") is not interface!" ); } if (! withExtensionAnnotation(type)) { throw new IllegalArgumentException("Extension type(" + type + ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!" ); } ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); if (loader == null) { EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type)); loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); } return loader; }Copy the code
private static final ConcurrentMap<Class<? >, ExtensionLoader<? >> EXTENSION_LOADERS = new ConcurrentHashMap<Class<? >, ExtensionLoader<? > > ();Copy the code

The ConcurrentMap is used, the ExtensionLoader is cached, and the key is the interface type

In EXTENSION_LOADERS. PutIfAbsent (type, new ExtensionLoader (type)); This sentence is called

private ExtensionLoader(Class<? > type) { this.type = type; objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); }Copy the code

ExtensionFactory implements SPI:

Adaptive getAdaptiveExtension ()

Get an extension class if the @Adaptive annotation is a decorator class on the class; If the annotation is a dynamic proxy class by method, such as the Protocol$Adaptive object.

@suppressWarnings ("unchecked") public T getAdaptiveExtension() {// First find the instance Object from the instance cache = cachedAdaptiveInstance.get(); / / used to cache the adaptive implementation class instance / / the cache does not exist Here have adopted two check if (instance = = null) {if (createAdaptiveInstanceError = = null) {synchronized (cachedAdaptiveInstance) { instance = cachedAdaptiveInstance.get(); If (instance == null) {try {// if null, create a new instance instance = createAdaptiveExtension(); cachedAdaptiveInstance.set(instance); } catch (Throwable t) { createAdaptiveInstanceError = t; throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t); } } } } else { throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError); } } return (T) instance; }Copy the code
@SuppressWarnings("unchecked") private T createAdaptiveExtension() { try { return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) { throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e); }}Copy the code

getAdaptiveExtensionClass()

private Class<? > getAdaptiveExtensionClass() { getExtensionClasses(); if (cachedAdaptiveClass ! = null) { return cachedAdaptiveClass; } return cachedAdaptiveClass = createAdaptiveExtensionClass(); } private Class<? > createAdaptiveExtensionClass () {/ / / assembly adaptive extension point class code String code = createAdaptiveExtensionClassCode (); ClassLoader ClassLoader = findClassLoader(); / / get the compiler defaults to using javassist com.alibaba.dubbo.common.compiler.Com piler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); // Convert code to Class return compiler.compile(code, classLoader); }Copy the code

Compiler structure diagram:

/ * * * assembly adaptive extension point class code @ return * * * / private String createAdaptiveExtensionClassCode () {} / / omitted codeCopy the code

injectExtension

Private T injectExtension(T instance) {try {if (objectFactory! = null) {// iterate over all methods of the instance for (Method Method: Instance.getclass ().getmethods ()) {// Must be public and set with argument if (method.getName().startswith ("set") && method.getParameterTypes().length == 1 && Modifier.isPublic(method.getModifiers())) { Class<? > pt = method.getParameterTypes()[0]; try { String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : ""; / / according to ExtensionFactor Object creation Object Object. = the objectFactory getExtension (pt, the property); if (object ! = null) { method.invoke(instance, object); } } catch (Exception e) { logger.error("fail to inject via method " + method.getName() + " of interface " + type.getName() + ": " + e.getMessage(), e); } } } } } catch (Exception e) { logger.error(e.getMessage(), e); } return instance; }Copy the code
Object object = objectFactory.getExtension(pt, property); 
Copy the code

Here we use spring as an example

  public <T> T getExtension(Class<T> type, String name) {
        for (ApplicationContext context : contexts) {
            if (context.containsBean(name)) {
                Object bean = context.getBean(name);
                if (type.isInstance(bean)) {
                    return (T) bean;
                }
            }
        }
        return null;
    }
Copy the code