SPI

The Service Provider Interface (SPI) is a built-in Service discovery mechanism in the JDK. It can be used to enable framework extensions and replacement components, mainly used by framework developers, such as the java.sqL. Driver Interface. Different vendors can make different implementations of the same interface. The core idea is decoupling

java SPI

Provide a Java SPI demo first

In the meta-INF /services directory, create a file named the full path of the interface. For example com. Test. Spi. PrintService file content in order to realize the full path of a class Such as: com. Test. Spi. PrintServiceImpl. If there are multiple implementation classes, use line breaks to distinguish them, as shown in the following example

ServiceLoader<Printservice> serviceLoader = ServiceLoader.load(Printservice.class); For (Printservice Printservice: serviceLoader) {// Specify the method printService.println (); }Copy the code

In the serviceLoader, all interface services are loaded. ServiceLoader also implements the Iterable interface, so it has iterator functionality. However, the ServiceLoader API does not provide a separate API, so each time the specified implementation class is retrieved, it must be iterated.

Have attribute PREFIX = “meta-INF /services/” in ServiceLoader; “Answers why our SPI configuration is placed under this directory.

At the same time, it is not safe to use instances of the ServiceLoader class in multiple concurrent threads.

Dubbo SPI

The Dubbo framework stands out because it has a wealth of extension points that developers can extend themselves. Some improvements must be made to Java’s SPI. Here’s a screenshot from the official website:

Use META-INF/services/ META-INF/dubbo/ META-INF/dubbo/internal/META-INF/dubbo/internal Key = value line segmentation, key used in @ SPI annotations Such as file com. Alibaba. Dubbo. Demo. The provider. The Printservice, content is

p=com.alibaba.dubbo.demo.provider.PrintserviceImpl
Copy the code
Printservice printservice = ExtensionLoader.getExtensionLoader(Printservice.class).getDefaultExtension();
printservice.println();
Copy the code

Meanwhile, annotations such as @SPI, @adaptive, and @Active are provided to improve the whole SPI ecosystem

To compare

To quote from the official website:

Dubbo improves on the following issues with the JDK standard SPI:

  • The DK-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
  • Added support for extension points IoC and AOP, where one extension point can directly setter for injection of other extension points

Dubbo SPI

The cache

The performance advantage of Dubbo SPI is that Dubbo SPI uses caching, two types of caching

  • The class cache caches the class into memory based on its configuration and does not directly initialize it all
  • Instance caching for performance reasons, instantiated objects are also cached and instantiated on demand.

Most frameworks use ConcurrentMap for in-memory caches. The details of dubbo’s cache are as follows

Private static final ConcurrentMap<Class<? >, ExtensionLoader<? >> EXTENSION_LOADERS = new ConcurrentHashMap<Class<? >, ExtensionLoader<? > > (); Private static final ConcurrentMap<Class<? >, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<? >, Object>(); // The following caches are non-static when cached in ExtensionLoader // the Wrapper Class cache Set<Class<? » CachedWrapperClasses Class<? > cachedAdaptiveClass // ConcurrentMap<String, Holder<Object» CAChedlnSTANCES // Can only have one Holder<Obj ect> cachedAdaptivelnstance // Extension Class and extension cache ConcurrentMap<Class<? >, String> cachedNames // Map<String, Activate> cachedNames // Map<String, Activate> cachedNames // Does not include adaptive extension classes and Wrapper classes Holder<Map<String, Class<? » > cachedClassesCopy the code

From the cache structure, you can see that there are roughly three types of SPI

  • The Wrapper class wraps the extension class
  • Adaptive extension classes modify classes directly with @Adaptive annotations
  • Normal extension classes, except for the two above, are normal wrapper classes

SPI annotations

Dubbo uses 3 annotations to play SPI, @spi @adaptive @activate, in the source code example code is as follows

Public interface LoadBalance {@adaptive (" LoadBalance ") <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException; }Copy the code

The interface modified by @spi represents an extension point, while @adaptive is Adaptive, where the value represents the key of a parameter in the URL. In the method of self-adaptation, the matched extension class will be selected based on the corresponding value in the URL. Value is an array, which represents a key does not exist, the current will be in order to look for, such as org. Apache. Dubbo. Remoting. Transporter# bind

  @Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY})
  Server bind(URL url, ChannelHandler handler) throws RemotingException;
Copy the code

In addition to getting a single adaptive class, dubbo provides the ability to enable multiple extension classes at the same time. @activate Annotation @activate can be tagged on classes, interfaces, enumerated classes, and methods. Most scenarios are used in filters, which form a filter chain

String[] group() default {}; String[] value() default {}; String[] before() default {}; String[] before() default {}; String[] after() default {}; String[] after() default {}; Int order() default 0;Copy the code

For example, in the source code

// The attachments of the RpcContext are set to the filter, which provides the function of implicit parameters to solve the problem that the url of the Dubbo service does not change after publication. The url can be modified dynamically through the implicit parameters, and such changes are not "persistent". @activate (group = Constants.CONSUMER, order = -10000) public class ConsumerContextFilter implements Filter {Copy the code

Dubbo SPI features

The extension classes contain four features: auto-wrap, auto-load, adaptive, and auto-activation, which are accomplished by the three SPI classes mentioned above.

Automatic packaging

This is done by wrapping the extension class with the Wrapper class, as shown below

public class ProtocolFilterWrapper implements Protocol { private final Protocol protocol; // Implement Protocol, but inject a Protocol into the constructor, Public ProtocolFilterWrapper(Protocol Protocol) {if (Protocol == null) {throw new IllegalArgumentException("protocol == null"); } this.protocol = protocol; }... }Copy the code

The Wrapper class also implements the extension point interface, but the Wrapper is not an actual implementation of the extension point. Its main use is to wrap extension points outside the real extension point implementation when they are returned from ExtensionLoader. That is, what is returned from ExtensionLoader is actually an instance of the Wrapper class, which holds the actual extension point implementation class.

The Wrapper class allows all extension point common logic to be moved into the Wrapper. The new Wrapper adds logic to all extension points, somewhat like AOP, in that Wrapper proxies extension points

Automatically.

In addition to constructor injection, setter methods are often used to set property values. The following are examples from the official website. There are two extension points, NAMELY, Mk and WheelMaker.

public interface CarMaker {
    Car makeCar();
}
 
public interface WheelMaker {
    Wheel makeWheel();
}
Copy the code
public class RaceCarMaker implements CarMaker {
    WheelMaker wheelMaker;
 
    public setWheelMaker(WheelMaker wheelMaker) {
        this.wheelMaker = wheelMaker;
    }
 
    public Car makeCar() {
        // ...
        Wheel wheel = wheelMaker.makeWheel();
        // ...
        return new RaceCar(wheel, ...);
    }
}
Copy the code

When ExtensionLoader loads the sphere’s extension point implementation of RaceCarMaker, setWheelMaker method WheelMaker is also an extension point that will inject WheelMaker’s implementation.

Another issue that arises here is how the ExtensionLoader decides which implementation of the dependency extension point to inject when it wants to inject it. In this case, which of multiple WheelMaker implementations to inject

The adaptive

That is, the use of @adaptive. When the @adaptive modification method is introduced above, the commonly used method is to select the Adaptive implementation class according to the key in the URL. When modifying a class with @adaptive, the entire class is used as the default implementation. Therefore, only one of the multiple implementation classes in the extension point can add @Adaptive. If multiple implementation classes have this annotation, an exception will be thrown :More than 1 Adaptive class Found

When no Adaptive class can be found after all keys of @Adaptive are compared, “hump rule” matching will be adopted. Dubbo will automatically separate the interface name according to the case of hump and connect it with symbols, which will be used as the name of the default implementation class. Such as org. Apache. Dubbo. XXX. YyylnvokerWpappep YyylnvokerWrapper will be converted to yyy. Invoker. Wrapper, in order to compare

Extension points are automatically activated

That’s the use of @activate, which we saw above

ExtensionLoader

ExtensionLoader is the main logical class for the entire extension mechanism. It is mainly divided into three entrances

  • GetExtension returnExtensionLoader<T>
  • GetActivateExtension returnList<T>
  • GetAdaptiveExtension return<T>

The flow chart is as follows: from In-depth Understanding of Apache Dubbo and Actual Combat

GetAdaptiveExtension generates an Adaptive class from the code string, typically named XXXX $Adaptive. Source code analysis will not write, feel too redundant, and more procrastination. Readers can browse through the three entrances by themselves. Here is an example of an adaptive class interface generated in my demo

@SPI("p")
public interface Printservice {
    @Adaptive({Constants.SERVER_KEY, "c"})
    void  println(URL url);

}
Copy the code
public class Printservice$Adaptive implements com.alibaba.dubbo.demo.provider.Printservice { public void println(com.alibaba.dubbo.common.URL arg0) { if (arg0 == null) throw new IllegalArgumentException("url == null"); com.alibaba.dubbo.common.URL url = arg0; /** * Note: if the @adaptive annotation does not pass in a key argument, it converts the class name to key by default. For example, AaaaBbbb converts to aAAA. GetParameter ("key1", "impll"); String extName = url.getParameter("server", url.getParameter("c", "p")); if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.demo.provider.Printservice)  name from url(" + url.toString() + ") use keys([server, c])"); com.alibaba.dubbo.demo.provider.Printservice extension = (com.alibaba.dubbo.demo.provider.Printservice)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.demo.provider.Printse rvice.class).getExtension(extName); extension.println(arg0); }}Copy the code

The above is the Adaptive class, and the corresponding implementation class can be selected with the URL key. Therefore, if both @SPI and @adaptive have values, it can be seen from the code that when the URL contains the key, the Adaptive class will be searched through the URL; if not, the default value in @SPI will be used. If there is no default value in the @spi annotation, convert the class name to a key and look for it, a “hump rule” match