In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-03-30 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Servers >
Share
Shulou(Shulou.com)05/31 Report--
This article mainly introduces "what are the characteristics and implementation principles of Dubbo SPI". In daily operation, I believe that many people have doubts about the characteristics and implementation principles of Dubbo SPI. The editor consulted all kinds of materials and sorted out simple and easy-to-use methods of operation. I hope it will be helpful to answer the doubts about "what are the characteristics and implementation principles of Dubbo SPI?" Next, please follow the editor to study!
I. Overview
SPI, whose full name is Service Provider Interface, is a mechanism by which components of modules refer to each other. The solution is usually for the provider to configure the full name of the interface implementation class in the specified file under classPath, which is read and loaded by the caller. When you need to replace a component, you just need to introduce a new JAR package and include the new implementation class and configuration file in it, and the caller's code does not need to be adjusted. An excellent SPI framework can provide priority selection for multiple implementation classes with a single interface, and the user can specify which implementation to choose.
Thanks to these capabilities, SPI provides a very good support for pluggable mechanism and dynamic expansion between modules.
This article will briefly introduce the SPI that comes with JDK, analyze the relationship between SPI and parental delegation, and then focus on the SPI mechanism of DUBBO; compare the differences between the two, and what additional capabilities DUBBO's SPI brings.
2. JDK comes with SPI
The provider creates a file named by the service interface in the META-INF/services/ directory of the classPath or jar package, and the caller loads the implementation class specified in the file content through java.util.ServiceLoader.
1. Code example
First define an interface Search
Search sample interface
Package com.example.studydemo.spi;public interface Search {void search ();}
The implementation class FileSearchImpl implements the interface
File search implementation class
Package com.example.studydemo.spi;public class FileSearchImpl implements Search {@ Override public void search () {System.out.println (File search);}}
The implementation class DataBaseSearchImpl implements the interface
Database search implementation class
Package com.example.studydemo.spi;public class DataBaseSearchImpl implements Search {@ Override public void search () {System.out.println (Database search);}}
Under the META-INF/services folder of the project, create the Search file
The contents of the file are:
Com.example.studydemo.spi.DataBaseSearchImplcom.example.studydemo.spi.FileSearchImpl
Test:
Import java.util.ServiceLoader;public class JavaSpiTest {public static void main (String [] args) {ServiceLoader searches = ServiceLoader.load (Search.class); searches.forEach (Search::search);}}
The result is:
two。 Simple analysis
ServiceLoader, as a service implementation lookup tool class provided by JDK, calls its own load method to load all the implementation classes of the Search interface, and then you can use the for loop to traverse the implementation class to make method calls.
There is a question: is the META-INF/services/ directory hard-coded, and are other paths okay? The answer is no.
Follow up to the ServiceLoader class, and the first line of code is private static final String PREFIX = "META-INF/services/", so the SPI configuration file can only be placed under the specified directory of the classPath or jar package.
File loading path for ServiceLoader
Public final class ServiceLoader implements Iterable {/ / hard-coded file path private static final String PREFIX = "META-INF/services/"; / / The class or interface representing the service being loaded private final Class service; / / The class loader used to locate, load, and instantiate providers private final ClassLoader loader
The use of JDK SPI is relatively simple, achieving the basic function of loading extension components, but has the following shortcomings:
You need to traverse all the implementations and instantiate them. If you want to find an implementation, you have to loop through it, one by one.
All the extension implementations are simply listed in the configuration file without naming them, making it difficult to refer to them accurately in the program
Extensions are dependent on each other, cannot be automatically injected and assembled, and do not provide IOC and AOP functions in context
It is difficult for extensions to integrate with other container frameworks, such as relying on bean in an external spring container, which is not supported by native JDK SPI.
3. SPI and parents delegate 1. Where is SPI loaded
Based on the principle of parental delegation of class loading, the class loaded internally by JDK should belong to the bootstrap classloader by default, so does the class loaded by the SPI mechanism also belong to bootstrap?
The answer is no, the native SPI mechanism specifies the classloader externally through the ServiceLoader.load method, or defaults to the classloader in the Thread.currentThread (). GetContextClassLoader () thread context, thus preventing class from being loaded into the bootstrap loader.
Did 2.SPI sabotage the parental appointment?
The essential meaning of parental delegation is to create a classLoader gap between the rt.jar package and the external class, that is, the class within the rt.jar should not be loaded by the external classLoader and the external class should not be loaded by the bootstrap.
SPI only provides a mechanism to interfere with external class file loading within JDK code, and does not force you to specify where to load; external class is still loaded by external classLoader, and without bridging this gap, there is no way to break parental delegation.
Class loader for native ServiceLoader
/ / specify the classloader public static ServiceLoader load (Class service,ClassLoader loader) / / the classloader public static ServiceLoader load (Class service) that takes the context before the thread by default. 4. Dubbo SPI
Dubbo draws lessons from the idea of Java SPI. Corresponding to JDK's ServiceLoader, Dubbo designs the ExtensionLoader class, which provides more powerful functions than JDK.
1. Basic concept
First of all, some basic concepts are introduced to give you a preliminary understanding.
Extension point (Extension Point): is an interface to Java.
Extension: the implementation class of the extension point
Extension instance (Extension Instance): an instance of an extension point implementation class.
Adaptive extended instance (Extension Adaptive Instance)
An adaptive extension instance is actually a proxy object of an extension class, which implements the extension point interface. When you call the interface method of the extension point, you decide which extension to use based on the actual parameters.
For example, an extension point for Search has a search method. There are two implementations, FileSearchImpl and DataBaseSearchImpl. When an adaptive instance of Search calls an interface method, it decides which implementation of Search to call based on the parameters in the search method.
If there is a name=FileSearchImpl in the method parameter, then the search method of FileSearchImpl is called. If name=DataBaseSearchImpl, the search method of DataBaseSearchImpl is called. Adaptive extension instances are widely used in Dubbo.
Every extension point in Dubbo can have an adaptive instance, and if we don't specify it manually using @ Adaptive, Dubbo will automatically generate one using the bytecode tool.
SPI Annotation
Acts on the interface of the extension point, indicating that the interface is an extension point and can be loaded by the ExtentionLoader of Dubbo
Adaptive
The @ Adaptive annotation can be used on a class or method. The method indicates that this is an adaptive method, and when Dubbo generates an adaptive instance, the dynamic proxy code is implanted in the method. Internally, the method determines which extension to use based on the parameters of the method.
The @ Adaptive annotation is used on the class to indicate that the implementation class is an adaptive class and belongs to an artificially specified scenario. Dubbo will not generate a proxy class for the SPI interface, such as AdaptiveCompiler, AdaptiveExtensionFactory, and so on.
The value of the @ Adaptive annotation is a string array, and the string in the array is the key value. The code should obtain the corresponding value according to the key value, and then load the corresponding extension instance. For example, new String [] {"key1", "key2"} means that the value of key1 will be found in URL first.
If found, use this value to load extension, if key1 does not have, look for the value of key2, if key2 does not have, use the default value of SPI annotation, if SPI annotation does not have default value, then divide the interface name into multiple parts according to the first letter capitalization
And then with'.' Delimited, for example, the org.apache.dubbo.xxx.YyyInvokerWrapper interface name becomes yyy.invoker.wrapper, and then look for it in URL with this name as key, and if it is still not found, an IllegalStateException exception is thrown.
ExtensionLoader
ServiceLoader, similar to Java SPI, is responsible for the loading and lifecycle maintenance of extensions. The functions of ExtensionLoader include parsing configuration files to load extension classes, generating extension instances and implementing IOC and AOP, creating adaptive extension, and so on.
Extension name
Unlike Java SPI, extensions in Dubbo have a name for referencing them in the application. such as
Registry=com.alibaba.dubbo.registry.integration.RegistryProtocol
Dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
Loading path
Java SPI loads the extension configuration from the / META-INF/services directory, and Dubbo loads the extension configuration file from the following path:
META-INF/dubbo/internal
META-INF/dubbo
META-INF/services
META-INF/dubbo is issued to developers, and the path META-INF/dubbo/internal is used to load expansion points within Dubbo.
two。 Code example
Define an interface, mark the SPI annotations of dubbo, give default values, and provide two extension implementation classes
Package com.example.studydemo.spi;@SPI ("dataBase") public interface Search {void search ();} public class FileSearchImpl implements Search {@ Override public void search () {System.out.println (File search);}} public class DataBaseSearchImpl implements Search {@ Override public void search () {System.out.println ("Database search");}}
Create a Search file under the META-INF/dubbo path
The contents of the document are as follows:
DataBase=com.example.studydemo.spi.DataBaseSearchImplfile=com.example.studydemo.spi.FileSearchImpl
Write a test class to test, as follows:
Public class DubboSpiTest {public static void main (String [] args) {ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader (Search.class); Search fileSearch = extensionLoader.getExtension ("file"); fileSearch.search (); Search dataBaseSearch = extensionLoader.getExtension ("dataBase"); dataBaseSearch.search (); System.out.println (extensionLoader.getDefaultExtensionName ()); Search defaultSearch = extensionLoader.getDefaultExtension (); defaultSearch.search ();}}
The result is:
From the point of view of the code example, Dubbo SPI and Java SPI are similar in these respects:
Interface and corresponding implementation
Configuration file
Loading class and loading concrete implementation
3. Source code analysis
Let's go deep into the source code to see how SPI works in Dubbo, and take the Protocol interface as an example.
/ / 1. Get the extension load object extensionLoader of Protocol, and obtain the corresponding adaptive extension class Protocol protocol = ExtensionLoader.getExtensionLoader (Protocol.class). GetAdaptiveExtension () from this load object; / / 2, get the corresponding extension class Protocol protocol = ExtensionLoader.getExtensionLoader (Protocol.class) .getExtension ("dubbo") according to the extension
Before obtaining the extension instance, you need to obtain the ExtensionLoader component of the Protocol interface. To obtain the corresponding Protocol instance Dubbo through ExtensionLoader actually creates a corresponding ExtensionLoader for each SPI interface.
ExtensionLoader component
Public static ExtensionLoader getExtensionLoader (Class type) {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!");} / / EXTENSION_LOADERS is ConcurrentMap and stores ExtensionLoader ExtensionLoader loader = (ExtensionLoader) EXTENSION_LOADERS.get (type) corresponding to Class; if (loader = = null) {EXTENSION_LOADERS.putIfAbsent (type, new ExtensionLoader (type)) Loader = (ExtensionLoader) EXTENSION_LOADERS.get (type);} return loader;}
EXTENSION_LOADERS is a ConcurrentMap, which takes the interface Protocol as the key and the ExtensionLoader object as the value; to save the loading class of the Protocol extension. The Protocol does not have its own interface to load the class when it is loaded for the first time, so one needs to be instantiated.
If you look at the operation new ExtensionLoader (type), here is the construction method of ExtensionLoader:
Rivate ExtensionLoader (Class type) {this.type = type; objectFactory = (type = = ExtensionFactory.class? Null: ExtensionLoader.getExtensionLoader (ExtensionFactory.class). GetAdaptiveExtension ();}
Each ExtensionLoader contains two values: type and objectFactory, in this case type is Protocol,objectFactory and ExtensionFactory.
For the ExtensionFactory interface, the objectFactory value in its load class is null.
For other interfaces, objectFactory is obtained through ExtensionLoader.getExtensionLoader (ExtensionFactory.class). GetAdaptiveExtension (); the function of objectFactory is to provide dependency injection objects for dubbo's IOC, which can be considered as an upper-level reference to multiple component containers in the process.
As this method is called more and more times, there will be more and more loader stored in EXTENSION_LOADERS.
Adaptive extension classes and IOC
Once you have the ExtensionLoader component, you can see how to get an adaptive extension instance.
Public T getAdaptiveExtension () {/ / cachedAdaptiveInstance is a cached adaptive object, so instance is null Object instance = cachedAdaptiveInstance.get (); if (instance = = null) {if (createAdaptiveInstanceError = = null) {synchronized (cachedAdaptiveInstance) {instance = cachedAdaptiveInstance.get () If (instance = = null) {try {/ / create adaptive object instance instance = createAdaptiveExtension (); / / put adaptive object into cache 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;}
First of all, it is obtained from the cachedAdaptiveInstance cache. There is no corresponding adaptive extension at the first call. You need to create an adaptive instance, and then put the instance into the cachedAdaptiveInstance cache after creation.
Creating an adaptive instance refers to the createAdaptiveExtension method, which consists of two parts: creating an adaptive extension class and instantiating it using reflection, and injecting attributes into the instance using IOC mechanism.
Private T createAdaptiveExtension () {try {/ / get the adaptive extension class and instantiate it with reflection, then inject the attribute value return injectExtension ((T) getAdaptiveExtensionClass (). NewInstance ());} catch (Exception e) {throw new IllegalStateException ("Can not create adaptive extenstion" + type + ", cause:" + e.getMessage (), e);}
Then analyze the getAdaptiveExtensionClass method, taking the Protocol interface as an example, which does the following things: get all the extension classes that implement the Protocol interface, return directly if there is an adaptive extension class, and create an adaptive extension class if not.
/ / the entry private Class getAdaptiveExtensionClass () {/ / 1 generated by the dynamic agent. Get all the extension classes getExtensionClasses () that implement the Protocol interface; / / 2. If there is an adaptive extension class, return if (cachedAdaptiveClass! = null) {return cachedAdaptiveClass;} / / 3. If not, create an adaptive extension class return cachedAdaptiveClass = createAdaptiveExtensionClass ();}
The getExtensionClasses method loads all the extension classes that implement the Protocol interface, and first gets them from the cache. If there is no such class in the cache, call the loadExtensionClasses method to load it and set it to the cache, as shown below:
Private Map > classes = cachedClasses.get (); if (classes = = null) {synchronized (cachedClasses) {classes = cachedClasses.get (); if (classes = = null) {/ / parse classes = loadExtensionClasses () from SPI configuration file; cachedClasses.set (classes);}} return classes;}
The loadExtensionClasses method is as follows: first, get the value value in the SPI annotation as the default extension name. In the Protocol API, the value of the SPI annotation is dubbo, so DubboProtocol is the default implementation extension of Protocol. Secondly, load the extension implementation of all the Protocol interfaces under the three configuration paths.
/ / this method has been synchronized with the getExtensionClasses method. Private Map > extensionClasses = new HashMap createAdaptiveExtensionClass () {/ / generate the class code String code = createAdaptiveExtensionClassCode (); / / find the class loader ClassLoader classLoader = findClassLoader (); / / get the compiler implementation class, here AdaptiveCompiler, with the Adaptive annotation com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader (com.alibaba.dubbo.common.compiler.Compiler.class). GetAdaptiveExtension () / / compile the class code to Class return compiler.compile (code, classLoader);}
The class code generated by the createAdaptiveExtensionClass method is as follows:
Package com.alibaba.dubbo.rpc; import com.alibaba.dubbo.common.extension.ExtensionLoader; public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {public void destroy () {throw new UnsupportedOperationException ("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy () of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!") } public int getDefaultPort () {throw new UnsupportedOperationException ("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort () of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");} public com.alibaba.dubbo.rpc.Invoker refer (java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException {if (arg1 = = null) throw new IllegalArgumentException ("url = = null") Com.alibaba.dubbo.common.URL url = arg1; String extName = (url.getProtocol ()) = null? "dubbo": url.getProtocol (); if (extName = = null) throw new IllegalStateException ("Fail to get extension (com.alibaba.dubbo.rpc.Protocol) name from url (" + url.toString () + ") use keys ([protocol])"); com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader (com.alibaba.dubbo.rpc.Protocol.class) .getExtension (extName) Return extension.refer (arg0, arg1);} public com.alibaba.dubbo.rpc.Exporter export (com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {if (arg0 = = null) throw new IllegalArgumentException ("com.alibaba.dubbo.rpc.Invoker argument = = null"); if (arg0.getUrl () = = null) throw new IllegalArgumentException ("com.alibaba.dubbo.rpc.Invoker argument getUrl () = = null") Com.alibaba.dubbo.common.URL url = arg0.getUrl (); String extName = (url.getProtocol ()) = null? "dubbo": url.getProtocol (); if (extName = = null) throw new IllegalStateException ("Fail to get extension (com.alibaba.dubbo.rpc.Protocol) name from url (" + url.toString () + ") use keys ([protocol])"); com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader (com.alibaba.dubbo.rpc.Protocol.class) .getExtension (extName); return extension.export (arg0) }}
The class Protocol$Adpative generated by the bytecode tool calls ExtensionLoader.getExtensionLoader (xxx) .getExtension (extName) at the end of the method to satisfy the adaptive dynamic nature of adaptive.
The incoming extName is the dynamic parameter obtained from the url. Users only need to specify the value of the protocol parameter in the URL representing the global context information of the DUBBO, and the adaptiveExtentionClass can dynamically adapt to different extended instances.
Then take a look at the attribute injection method injectExtension, which deals with the set method of public with only one parameter, and uses reflection to make method calls to realize attribute injection. This method is the key for Dubbo SPI to realize IOC function.
Private T injectExtension (T instance) {try {if (objectFactory! = null) {for (Method method: instance.getClass (). GetMethods ()) {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): ""; Object object = objectFactory.getExtension (pt, 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
Dubbo IOC injects dependencies through set methods. Dubbo first obtains all the methods of the instance through reflection, and then traverses the method list to check whether the method name has the characteristics of set method. If so, the dependent object is obtained through ObjectFactory.
Finally, the set method is called through reflection to set the dependency to the target object. ObjectFactory was created when the load class ExtensionLoader was created, and since @ Adaptive is typed on the class AdaptiveExtensionFactory, this is AdaptiveExtensionFactory.
AdaptiveExtensionFactory holds a collection of all ExtensionFactory objects. The default object factories implemented within dubbo are SpiExtensionFactory and SpringExtensionFactory. They are sorted by TreeSet, and the search order is first obtained from SpiExtensionFactory, if the return is empty, from SpringExtensionFactory.
/ / the Adaptive notes indicate that the class is adaptive, and there is no need for the program to create a proxy class @ Adaptivepublic class AdaptiveExtensionFactory implements ExtensionFactory {/ / factories has the implementation object private final List factories; public AdaptiveExtensionFactory () {ExtensionLoader loader = ExtensionLoader.getExtensionLoader (ExtensionFactory.class); List list = new ArrayList (); for (String name: loader.getSupportedExtensions ()) {list.add (loader.getExtension (name)) } factories = Collections.unmodifiableList (list);} / / traverses the factories when searching, getting it first from SpiExtensionFactory and then from SpringExtensionFactory, because the use of TreeSet in the getSupportedExtensions method has been sorted during initialization, as shown in the following figure public T getExtension (Class type, String name) {for (ExtensionFactory factory: factories) {T extension = factory.getExtension (type, name) If (extension! = null) {return extension;}} return null;}} public Set getSupportedExtensions () {Map 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, (T) clazz.newInstance ()); instance = (T) EXTENSION_INSTANCES.get (clazz);} / / attribute injection injectExtension (instance); Set
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.