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
- 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)
- @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