Through the study of this article, we can understand the characteristics and implementation principle of Dubbo SPI, and hope to have some inspiration for everyone’s development and design.
An overview,
SPI, also known as the Service Provider Interface, is a mechanism by which components of modules reference each other. The usual scenario is for the provider to configure the full name of the interface implementation class in a specified file under the classPath, which is read and loaded by the caller. When a component needs to be replaced, it simply introduces a new JAR package and includes the new implementation class and configuration file in it, without any adjustments to the caller’s code. A good SPI framework can provide priority selection for a single interface with multiple implementation classes, with the user specifying which implementation to choose.
Thanks to these capabilities, SPI provides excellent support for pluggable mechanisms and dynamic scaling between modules.
This article will briefly introduce the JDK’s own SPI, analyze the relationship between SPI and parent delegation, and then focus on the analysis of DUBBO’s SPI mechanism. Compare the differences between the two and what additional capabilities DUBBO’s SPI brings.
The JDK comes with an SPI
The provider creates a file named service interface in the META-INF/services/ directory of the classPath or JAR package, and the caller loads the implementation classes specified in the file contents via java.util.serviceloader.
1. Code examples
- Start by defining an interface Search
Search sample interface
package com.example.studydemo.spi;
public interface Search {
void search();
}
Copy the code
- Implementation class FileSearchImpl implements this interface
File search implementation class
package com.example.studydemo.spi; Public class FileSearchImpl implements Search {@override public void Search () {system.out.println (" implements Search "); }}Copy the code
- The implementation class DataBaseSearchImpl implements this interface
Database search implementation class
package com.example.studydemo.spi; Public class DataBaseSearchImpl implements Search {@override public void Search () {system.out.println (" implements Search "); }}Copy the code
- In the meta-INF /services folder of the project, create the Search file
The contents of the document are as follows:
com.example.studydemo.spi.DataBaseSearchImpl
com.example.studydemo.spi.FileSearchImpl
Copy the code
Testing:
import java.util.ServiceLoader; public class JavaSpiTest { public static void main(String[] args) { ServiceLoader<Search> searches = ServiceLoader.load(Search.class); searches.forEach(Search::search); }}Copy the code
The result is:
2. Simple analysis
The ServiceLoader, as a service implementation lookup class provided by the JDK, calls its own load method to load all implementation classes of the Search interface, which can then be used to iterate through the implementation class for method calls.
One question: is the meta-INF /services/ directory hard coded? Will other paths work? The answer is no.
Private static final String PREFIX = “meta-INF /services/” So the SPI configuration file can only be placed in the specified directory of the classPath or JAR package.
Path to load the ServiceLoader file
Public final class ServiceLoader<S> implements Iterable<S> {// Hardcode path private static final String PREFIX = "META-INF/services/"; // The class or interface representing the service being loaded private final Class<S> service; // The class loader used to locate, load, and instantiate providers private final ClassLoader loader;Copy the code
JDK SPI is relatively simple to use and does the basic function of loading extension components, but it has the following shortcomings:
- You need to go through all the implementations and instantiate them, and you can only find one implementation by looping through it, matching one by one;
- The configuration file simply lists all extension implementations without naming them, making it difficult to refer to them accurately in the application.
- Extensions depend on each other, do not automate injection and assembly, and do not provide contextual IOC and AOP functionality;
- Extensions are difficult to integrate with other container frameworks, such as extensions that rely on beans in an external Spring container that are not supported by the native JDK SPI.
SPI and parental delegation
1. Where SPI is loaded
Classes loaded internally by the JDK belong to the Bootstrap class loader by default. Do classes loaded by the SPI mechanism also belong to bootstrap?
The answer is no. The native SPI mechanism specifies class loaders externally via the Serviceloader.load method, or a class loader that defaults to thread.currentThread ().getContextClassLoader() Thread context. This prevents the class from being loaded into the Bootstrap loader.
2. Does SPI break parental delegation
The essence of parental delegation is to create a classLoader gap between the rt.jar package and the external class, that is, classes inside rt.jar should not be loaded by the external classLoader, and external classes should not be loaded by bootstrap.
SPI only provides a mechanism for interfering with the loading of external class files within JDK code. It does not force you to specify where to load them. The external class is still loaded by the external classLoader, so we don’t cross this gap and break the parent delegate.
A classloader for the native ServiceLoader
Public static <S> load(Class<S> service,ClassLoader) public static <S> load(Class<S> service,ClassLoader) ServiceLoader<S> load(Class<S> service)Copy the code
Four, Dubbo SPI
Dubbo borrowed ideas from the Java SPI and designed the ExtensionLoader class as a counterpart to the JDK’s ServiceLoader, which provides more power than the JDK.
1. Basic concepts
First introduce some basic concepts, so that we have a preliminary cognition.
- Extension Point: ** is a Java interface.
- Extension: the implementation class of the Extension point
- Extension Instance: an Instance of the Extension point implementation class.
- Extension Adaptive Instance
An adaptive extension instance is essentially a proxy object for an extension class that implements an extension point interface. When an extension point’s interface method is called, the actual parameters determine which extension to use.
For example, a Search extension point has a Search method. There are two implementations FileSearchImpl and DataBaseSearchImpl. When an adaptive instance of Search calls an interface method, it determines which implementation of Search to invoke based on parameters in the Search method.
If name=FileSearchImpl is in the method argument, 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.
Each extension point in Dubbo can have an Adaptive instance, and if we don’t manually specify it using @Adaptive, Dubbo will automatically generate one using bytecode tools.
-
SPI Annotation
Applied to the interface of an extension point, indicating that the interface is an extension point that can be loaded by Dubbo’s ExtentionLoader
-
Adaptive
The @adaptive annotation can be used on classes or methods. To indicate that this is an adaptive method, Dubbo builds dynamic proxy code into the method when it generates adaptive instances. The method itself decides which extension to use based on the method’s parameters.
The @adaptive annotation is used on the class to indicate that the implementation class is an Adaptive class, which belongs to the artificially specified scene. Therefore, Dubbo will not generate a proxy class for the SPI interface. The most typical applications are AdaptiveCompiler, AdaptiveExtensionFactory, etc.
The Value of the @Adaptive annotation is an array of strings, 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”} will look for the value of key1 in the URL first,
If found, use this value to load extension, if key1 is not present, look for the value of key2, if key2 is not present either, use the default value of the SPI annotation, and if the SPI annotation does not have a default value, split the interface name into multiple parts by uppercase,
And then to ‘. ‘space, such as org. Apache. Dubbo. XXX. YyyInvokerWrapper interface name becomes yyy. Invoker. Wrapper, then this name as the key to the URL to find, If still not found, IllegalStateException is thrown.
-
ExtensionLoader is similar to the Java SPI ServiceLoader and is responsible for the loading and lifecycle maintenance of extensions. ExtensionLoader’s functions include parsing configuration files to load extension classes, generating extension instances and implementing IOC and AOP, and creating adaptive extensions, which will be highlighted below.
-
Unlike the Java SPI, extensions in Dubbo have a name that you can use to refer to them in your application. Such as the registry = com. Alibaba. Dubbo. Registry. Integration. RegistryProtocol dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
-
Java SPI loads the extension configuration from the/meta-INF /services directory. Dubbo loads the extension configuration file from the following path: Meta-inf /dubbo/internal meta-INF /dubbo meta-INF /services The meta-INF /dubbo/internal path is used to load extension points inside dubbo.
2. Code examples
Define an interface with SPI annotations on dubbo, assign default values, and provide two Extension implementation classes
package com.example.studydemo.spi;
@SPI("dataBase")
public interface Search {
void search();
}
Copy the code
Public class FileSearchImpl implements Search {@override public void Search () {system.out.println (" implements Search "); }}Copy the code
Public class DataBaseSearchImpl implements Search {@override public void Search () {system.out.println (" implements Search "); }}Copy the code
Create the Search file in meta-INF /dubbo
The content of the document is as follows:
dataBase=com.example.studydemo.spi.DataBaseSearchImpl
file=com.example.studydemo.spi.FileSearchImpl
Copy the code
Write a test class to test, the content is as follows:
public class DubboSpiTest { public static void main(String[] args) { ExtensionLoader<Search> 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(); }}Copy the code
The result is:
From the code examples, Dubbo SPI is similar to Java SPI in these respects:
- Interface and corresponding implementation
- The configuration file
- Load the class and load the concrete implementation
3. Source code analysis
Let’s dive into the source code to see how SPI works in Dubbo, using the Protocol interface as an example.
// get the Protocol extensionLoader. By this load object to obtain the corresponding adaptive extended Protocol Protocol = ExtensionLoader. GetExtensionLoader (Protocol. The class). GetAdaptiveExtension (); / / 2, according to the extension for the expansion of the corresponding Protocol Protocol = ExtensionLoader. GetExtensionLoader (Protocol. The class). GetExtension (" dubbo ");Copy the code
Before obtaining the extended instance, it is necessary to obtain the ExtensionLoader component of the Protocol interface. Dubbo obtains the corresponding Protocol instance through ExtensionLoader, which actually creates a corresponding ExtensionLoader for each SPI interface.
ExtensionLoader components
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> 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, 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
EXTENSION_LOADERS is a ConcurrentMap with the interface Protocol as key and ExtensionLoader object as value. Protocol does not have its own interface loading class when it is first loaded. You need to instantiate one.
New ExtensionLoader(type);
rivate ExtensionLoader(Class<? > type) { this.type = type; objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); }Copy the code
Each ExtensionLoader contains two values: type and objectFactory. In this case, Type is Protocol and objectFactory is ExtensionFactory.
For the ExtensionFactory interface, the objectFactory value in its load class is null.
For other interfaces, the objectFactory by ExtensionLoader. GetExtensionLoader (ExtensionFactory. Class). GetAdaptiveExtension () to obtain; The purpose of the objectFactory is to provide a dependency injection object for Dubbo’s IOC, which can be thought of as a top-level reference to multiple component containers in the process,
As this method is called more times, more loaders are stored in EXTENSION_LOADERS.
Adaptive extension classes and IOC
Now that you have the ExtensionLoader component, look at how to get an adaptive extension instance.
Public T getAdaptiveExtension() {//cachedAdaptiveInstance is a cached adaptive object. So the instance is null Object instance = cachedAdaptiveInstance. The get (); if (instance == null) { if(createAdaptiveInstanceError == null) { synchronized (cachedAdaptiveInstance) { instance = cachedAdaptiveInstance.get(); If (instance == null) {try {// Create an instance of the adaptive object instance = createAdaptiveExtension(); / / put the adaptive object in the 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; }Copy the code
It is first obtained from the cachedAdaptiveInstance cache. When it is called for the first time, there is no corresponding adaptive extension. You need to create an adaptive instance, and then put the instance into the cachedAdaptiveInstance cache.
To create an adaptive instance, refer to the createAdaptiveExtension method, which consists of creating an adaptive extension class and instantiating it using reflection, and injecting properties into the instance using the IOC mechanism.
Private T createAdaptiveExtension() {try {// Get the adaptive extension class and instantiate it with reflection, And then injected into the attribute value return injectExtension ((T) getAdaptiveExtensionClass (). The newInstance ()); } catch (Exception e) { throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e); }}Copy the code
Analysis getAdaptiveExtensionClass method, Protocol interface, for example, the method to do the following things: obtain all realize the expansion of the Protocol interface, if there are adaptive extended returned directly, if there is no adaptive extended class is created.
// The dynamic proxy generates an entry private Class<? > getAdaptiveExtensionClass() { //1. Get getExtensionClasses(), all extended classes that implement the Protocol interface; //2. Return if (cachedAdaptiveClass! = null) { return cachedAdaptiveClass; } / / 3. If not, create an adaptive extended class return cachedAdaptiveClass = createAdaptiveExtensionClass (); }Copy the code
The getExtensionClasses method loads all extended classes that implement the Protocol interface, first from the cache, and then calls the loadExtensionClasses method to load and set them into the cache, as shown in the following figure:
private Map<String, Class<? >> getExtensionClasses() {// Get Map<String, Class<? >> classes = cachedClasses.get(); if (classes == null) { synchronized (cachedClasses) { classes = cachedClasses.get(); If (classes == null) {// Resolve classes = loadExtensionClasses() from SPI configuration files; cachedClasses.set(classes); } } } return classes; }Copy the code
The loadExtensionClasses method is as follows: first get the SPI annotation value as the default extension name. In the Protocol interface, the SPI annotation value is dubbo, so DubboProtocol is the default implementation extension of Protocol. Next, load the extended implementation of all Protocol interfaces under the three configuration paths.
// This method is already synchronized with the getExtensionClasses method. private Map<String, Class<? >> loadExtensionClasses() { final SPI defaultAnnotation = type.getAnnotation(SPI.class); if(defaultAnnotation ! = null) { String value = defaultAnnotation.value(); if(value ! = null && (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]; } } Map<String, Class<? >> extensionClasses = new HashMap<String, Class<? > > (); // Load loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY) from three paths; loadFile(extensionClasses, DUBBO_DIRECTORY); loadFile(extensionClasses, SERVICES_DIRECTORY); return extensionClasses; } private static final String SERVICES_DIRECTORY = "META-INF/services/"; private static final String DUBBO_DIRECTORY = "META-INF/dubbo/"; private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";Copy the code
One of the points of concern in the implementation under the load configuration path is that if there is a Adaptive annotation on one of the implementation classes indicating that the user specified an Adaptive extension class, the implementation class will be assigned to cachedAdaptiveClass. In getAdaptiveExtensionClass approach will be returned directly.
If the variable is empty, the adaptive extension class needs to be created using a bytecode tool.
private Class<? > createAdaptiveExtensionClass () {/ / generated code String code = createAdaptiveExtensionClassCode (); ClassLoader = findClassLoader(); // Get compiler implementation class, in this case AdaptiveCompiler, Such are the Adaptive annotation com.alibaba.dubbo.common.compiler.Com piler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); // Class return compiler.compile(code, classLoader); }Copy the code
CreateAdaptiveExtensionClass method generated class code 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); }}Copy the code
Class generated by the bytecode instrumentation Protocol $Adpative. At the end of the method calls the ExtensionLoader getExtensionLoader (XXX). GetExtension (extName) to meet the adaptive adaptive dynamic characteristics.
The extName passed in is the dynamic parameter obtained from the URL. Users only need to specify the value of the protocol parameter in the URL that represents the global context information of DUBBO. AdaptiveExtentionClass can dynamically adapt different extension instances.
The property injection method injectExtension is the key to Dubbo SPI’s IOC function. It handles the set method of public with only one parameter and uses reflection to implement the property injection.
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;Copy the code
Dubbo IOC uses the set method to inject dependencies. Dubbo first gets all the methods of the instance through reflection and then iterates through the list of methods to check if the method name has set characteristics. If so, get the dependent object through ObjectFactory.
Finally, the dependency is set to the target object by calling the set method through reflection. ObjectFactory was created when the loading class ExtensionLoader was created, because @Adaptive is built on AdaptiveExtensionFactory, so it is AdaptiveExtensionFactory.
AdaptiveExtensionFactory holds a collection of all ExtensionFactory objects. The default object factories implemented internally by Dubbo are SpiExtensionFactory and SpringExtensionFactory. They are sorted by TreeSet, and the search order is first fetched from SpiExtensionFactory or, if returned, from SpringExtensionFactory.
// The Adaptive annotation indicates that the class is Adaptive. @Adaptive Public Class AdaptiveExtensionFactory implements ExtensionFactory { Private final List<ExtensionFactory> Factories; public AdaptiveExtensionFactory() { ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class); List<ExtensionFactory> list = new ArrayList<ExtensionFactory>(); for (String name : loader.getSupportedExtensions()) { list.add(loader.getExtension(name)); } factories = Collections.unmodifiableList(list); } // The factories are iterated, first from SpiExtensionFactory, then from SpringExtensionFactory, The reason is that TreeSet is sorted in the getSupportedExtensions method during initialization. Public <T> T getExtension(Class<T> type, String name) {for (ExtensionFactory factory: factories) { T extension = factory.getExtension(type, name); if (extension ! = null) { return extension; } } return null; }}Copy the code
public Set<String> getSupportedExtensions() { Map<String, Class<? >> clazzes = getExtensionClasses(); return Collections.unmodifiableSet(new TreeSet<String>(clazzes.keySet())); }Copy the code
Although there is suspicion of over-design, we have to admire the ingenuity of dubbo SPI’s design.
- @adaptive annotation is provided, which can be added to the method to dynamically adapt to different extension instances through parameters. You can also specify adaptive extension classes directly by adding them to the class.
- The AdaptiveExtensionFactory is used to unify different containers in the process, treating ExtensionLoader itself as an independent container, which will be looked up from the Spring container and ExtensionLoader container respectively during dependency injection.
Extended instances and AOP
The getExtension method is simple, and the focus is on the createExtension method, which creates an extension instance based on the extension name.
public T getExtension(String name) { if (name == null || name.length() == 0) throw new IllegalArgumentException("Extension name == null"); if ("true".equals(name)) { return getDefaultExtension(); } Holder<Object> holder = cachedInstances.get(name); if (holder == null) { cachedInstances.putIfAbsent(name, new Holder<Object>()); holder = cachedInstances.get(name); } Object instance = holder.get(); if (instance == null) { synchronized (holder) { instance = holder.get(); Instance = createExtension(name); if (instance == null) {// createExtension instance by extension name. holder.set(instance); } } } return (T) instance; }Copy the code
The getExtensionClasses method gets all the implementation classes of the interface, and then gets the corresponding Class by name. The implementation class is then instantiated via clazz.newinstance (), calling injectExtension to inject properties into the instance.
Private T createExtension(String name) {//getExtensionClasses 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, (T) clazz.newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } // The property is injected injectExtension(instance); Set<Class<? >> wrapperClasses = cachedWrapperClasses; if (wrapperClasses ! = null && wrapperClasses.size() > 0) { for (Class<? > wrapperClass : WrapperClasses) {/ / to create wrapper classes and attributes into the instance = injectExtension ((T) wrapperClass. GetConstructor (type). NewInstance (instance)); } } return instance; } catch (Throwable t) { throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type + ") could not be instantiated: " + t.getMessage(), t); }}Copy the code
At the end of the method is a bit of processing logic for the WrapperClass WrapperClass, which returns an instance if the interface has a WrapperClass implementation. The key to implementing AOP is the WrapperClass mechanism, which determines whether an extended class is WrapperClass based on whether its constructor function contains the current interface arguments.
If there is a wrapperClass, it is considered a wrapperClass, and the resulting instance is the result of multiple wrapperClass layers. AOP functionality can be easily implemented by coding section-oriented code into each wrapperClass.
Activate extension
The getActivateExtension method corresponding to ExtensionLoader intelligently filters the part of the extension collection that you want based on multiple filtering criteria.
GetActivateExtension method
public List<T> getActivateExtension(URL url, String[] names, String group);
Copy the code
First, this method will only return extension classes with an Activate annotation, but not all extension classes with an annotation will be returned.
Names specifies the part of the extension class that is required. Non-specified extension classes must satisfy both the group filter and the key filter specified in the Activate annotation. Non-specified extension classes are sorted according to the order specified in the Activate annotation.
The return result of getActivateExtension is the sum of the above two extension classes.
Activate annotation class
*/ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, Elementtype. METHOD}) public @interface Activate {/** * Group filter criteria. */ String[] group() default {}; /** * Key Filter criteria. In the parameter Key of the URL containing {@link ExtensionLoader#getActivateExtension}, return the extension. */ String[] value() default {}; /** ** The sort information can be omitted. */ String[] before() default {}; /** ** The sort information can be omitted. */ String[] after() default {}; /** ** The sort information can be omitted. */ int order() default 0; }Copy the code
The most typical application of active Extension is to obtain the filter chain when RPC Invoke. Various filters have a clear execution priority, and some filters can also be artificially added. Filters can also be grouped and filtered according to service providers and consumers.
Dubbo Invoke retrieves the filter chain
List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), Constants.SERVICE_FILTER_KEY, Constants.PROVIDER);
Copy the code
In the case of TokenFilter, the annotation @activate (group = constants.provider, value = constants.token_key) means that the filter will only be loaded when the service is provided, At the same time, the system verifies whether the url of the registered address contains a token parameter. If there is a token, the server indicates that token authentication is required during registration. Therefore, the filter needs to be loaded.
Otherwise, do not load; After the filter is loaded, the execution logic obtains the token preset during server registration from the URL, and then obtains the remote token set by the consumer from the attachments of RPC requests, and compares whether the two are consistent. If they are inconsistent, RPCExeption is thrown to prevent the normal invocation of the consumer.
Five, the summary
Almost all of Dubbo’s interfaces have extension points reserved for different implementations based on user parameters. If you want to add a new interface implementation, you simply add a configuration file according to SPI specifications and point to the new implementation.
All user-configured Dubbo attributes are reflected in URL global context parameters. URL runs through the entire Dubbo architecture and is the link between each layer component of Dubbo to call each other.
To summarize the advantages of Dubbo SPI over Java SPI:
- Dubbo’s extension mechanism designs default values, and each extension class has its own name for easy lookup.
- Dubbo’s extension mechanism supports IOC, AOP and other advanced features.
- Dubbo’s extension mechanism is compatible with third-party IOC containers and supports Spring beans by default, but can also be extended to other containers.
- Dubbo’s extension class implements dynamic proxy functionality through the @Adaptive annotation, and even more powerful is the ability to map multiple different extension classes from a single proxy.
- Dubbo extension class implements grouping, filtering, and sorting functions of different extension classes through @Activate annotation, which can better adapt to complex service scenarios.
Xie Xiaopeng