This article will have two parts: Dubbo SPI extensions and adaptive extensions
Dubbo SPI extension
Let’s take a look at an example to understand what an SPI extension is. This example is also available on the website
// Specify a Root interface
public interface Root {
// There is only one method
void sayHello(a); } the implementation classpublic class SpringRoot implements Root {
@Override
public void sayHello(a) {
System.out.println("SpringRoot..."); }}public class DubboRoot implements Root {
@Override
public void sayHello(a) {
System.out.println("DubboRoot..."); }}Copy the code
Root: dubo.learn.basic. Root: dubo.learn.basic. Root: dubo.learn.basic. Root: dubo.learn.
dubbo.learn.basic.SpringRoot
dubbo.learn.basic.DubboRoot
Copy the code
Finally, write a startup class
public class RootTest {
public static void main(String[] args) { ServiceLoader<Root> serviceLoader = ServiceLoader.load(Root.class); serviceLoader.forEach(Root::sayHello); }}Copy the code
The printed result is:
SpringRoot…
DubboRoot…
ServiceLoader loads all the implementation classes, which is not what Dubbo wanted. Dubbo loads on demand, so Dubbo has implemented an SPI mechanism of its own.
The Dubbo SPI is somewhat different from the above examples in terms of usage:
-
The first step is to annotate the Root interface with @spi
-
Dubbo. Learn. Basic. The content of the Root file form is the key – value, such as springRoot = com. Hzed. Dubbo. Learn. Basic. SpringRoot
And then we’re going to change the startup class
public class RootTest {
public static void main(String[] args) {
ExtensionLoader<Root> extensionLoader = ExtensionLoader.getExtensionLoader(Root.class);
Root springRoot = extensionLoader.getExtension("springRoot"); springRoot.sayHello(); }}Copy the code
The printed result is:
SpringRoot…
The core of Dubbo SPI is ExtensionLoader, so let’s analyze ExtensionLoader first
getExtensionLoader
The first step is to create the ExtensionLoader, passing in the Class type
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!");
}
// Get it from the cache first, if the cache does not have it, just new
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
The constructor of ExtensionLoader is private, so we can’t directly new the object. Instead, we get the object through static methods. Let’s look at the constructor of ExtensionLoader
private ExtensionLoader(Class
type) {
this.type = type;
Root.class; root.class; The second is extensionFactory.class; But in our demo, objectFactory is empty, why is it empty? We'll talk about adaptive scaling later
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
Copy the code
The constructor is actually very simple. In this case, you can actually ignore objectFactory, so the type of the current object is root.class. Next, look at the getExtension method
public T getExtension(String name) {
if (name == null || name.length() == 0)
throw new IllegalArgumentException("Extension name == null");
if ("true".equals(name)) {
return getDefaultExtension();
}
// The target object is encapsulated as a holder
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<Object>());
holder = cachedInstances.get(name);
}
// Double check
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
// If the cache does not exist, create the extension classinstance = createExtension(name); holder.set(instance); }}}return (T) instance;
}
Copy the code
createExtension
private T createExtension(String name) {
/ / is to load the file first, and then get extension Class, according to the names of the specified Class, about load and parse the file https://dubbo.apache.org/zh/docs/v2.7/dev/source/dubbo-spi/ website has a detailed analysisClass<? > clazz = getExtensionClasses().get(name);if (clazz == null) {
throw findException(name);
}
try {
// First fetch from cache
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
// If the cache does not exist, create an instance
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
// dependency injectioninjectExtension(instance); Set<Class<? >> wrapperClasses = cachedWrapperClasses;if(wrapperClasses ! =null && !wrapperClasses.isEmpty()) {
for (Class<?> wrapperClass : wrapperClasses) {
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
We can skip getExtensionClasses() as long as the details don’t interfere with our understanding. GetExtensionClasses () does not affect our understanding of this method. So you can skip it. If you’re interested in this, you can go ahead and explore it
There’s an injectExtension here, which is dependency injection for Dubbo
private T injectExtension(T instance) {
try {
// objectFactory is ExtensionFactory, which may be Spring ApplicationContext and Dubbo's SpiExtensionFactory, AdaptiveExtensionFactory, Find dependent objects through these containers
if(objectFactory ! =null) {
for (Method method : instance.getClass().getMethods()) {
if (method.getName().startsWith("set")
&& method.getParameterTypes().length == 1
&& Modifier.isPublic(method.getModifiers())) {
/**
* Check {@link DisableInject} to see if we need auto injection for this property
*/
if(method.getAnnotation(DisableInject.class) ! =null) {
continue; } 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) {
// Dubbo uses the set method to inject the dependent object into instancemethod.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
At this point, the overall framework of Dubbo SPI is basically laid out
SPI adaptive extension
What is adaptive? The description on the website reads as follows:
Sometimes, extensions do not want to be loaded during the framework startup phase, but rather want to be loaded based on runtime parameters when the extension method is called. This may sound contradictory. Extension methods cannot be called unless the extension is loaded (except for static methods). The extension cannot be loaded until the extension method is called. Dubbo solves this paradox with an adaptive extension mechanism.
Then there’s WheelMaker’s example. (You may still not know what adaptive is after reading it.)
When an extension method is called, it is loaded according to runtime parameters. In other words, when we use an extension class, we decide which extension class to use as the implementation class of the interface when we call the interface method
Look at the ServiceConfig
public class ServiceConfig<T> extends AbstractServiceConfig {
/ /...
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
/ /...
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {... Exporter<? > exporter = protocol.export(wrapperInvoker); . }/ /...
}
Copy the code
The doExportUrlsFor1Protocol method is used for exporting services, etc. <dubbo:protocol name=”dubbo” port=”2880″ /> <dubbo:protocol name=”dubbo” port=”2880″ />
That is, the code doesn’t know until it reaches the protocol.export(wrapperInvoker) line that the specific extension class to use is DubboProtocol. Protocol is assigned at the beginning of the code. What class is assigned? With this question in mind, start analyzing the adaptive extension framework.
The key point of entry, is above the Protocol Protocol = ExtensionLoader. GetExtensionLoader (Protocol. The class). GetAdaptiveExtension ()
Because getExtensionLoader creates ExtensionLoader objects, let’s look at the constructor again
private ExtensionLoader(Class
type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
Copy the code
Said earlier that the constructor is called twice, that’s because the second is performed ExtensionLoader getExtensionLoader (ExtensionFactory. Class), when to perform here, Type is extensionFactory. class, objectFactory is null, that is, the extension class of ExtensionFactory is loaded first
The following code analysis is based on the extension class to load ExtensionFactory
getAdaptiveExtension
public T getAdaptiveExtension(a) {
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if (createAdaptiveInstanceError == null) {
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
// Nothing else to say, here is to create the extension class
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
createAdaptiveExtension
private T createAdaptiveExtension(a) {
try {
/ / dependency injection is not the point, see getAdaptiveExtensionClass directly
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: "+ e.getMessage(), e); }}Copy the code
getAdaptiveExtensionClass
privateClass<? > getAdaptiveExtensionClass() {// The extension class is also loaded through a file
getExtensionClasses();
if(cachedAdaptiveClass ! =null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
privateClass<? > createAdaptiveExtensionClass() {// This method is very long with many details. I suggest you go directly to the official website. Details are not our purpose this time.
// But the result returned is exactly what we care about
String code = createAdaptiveExtensionClassCode();
ClassLoader classLoader = findClassLoader();
com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
return compiler.compile(code, classLoader);
}
Copy the code
When the code executes getExtensionClasses(), the ExtensionFactory implementation class AdaptiveExtensionFactory, which is annotated @Adaptive, has a branch judgment in the method. If the class is annotated by this annotation, CachedAdaptiveClass, so cachedAdaptiveClass is not empty, it just returns cachedAdaptiveClass, which is AdaptiveExtensionFactory.
At this point, the method getAdaptiveExtensionClass () returns cachedAdaptiveClass, then through getAdaptiveExtensionClass (). The newInstance () to create objects, then complete the dependency injection.
At this point, the objectFactory completes the assignment
AdaptiveExtensionFactory
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
private final List<ExtensionFactory> factories;
public AdaptiveExtensionFactory(a) {
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);
}
@Override
public <T> T getExtension(Class<T> type, String name) {
for (ExtensionFactory factory : factories) {
T extension = factory.getExtension(type, name);
if(extension ! =null) {
returnextension; }}return null; }}Copy the code
The constructor is called getExtensionClasses(), to add ExtensionFactory to the list. Or all extension classes of ExtensionFactory
The getExtension method directly calls the getExtension method of ExtensionFactory. Let’s look at the implementation of SpiExtensionFactory
public class SpiExtensionFactory implements ExtensionFactory {
@Override
public <T> T getExtension(Class<T> type, String name) {
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
if(! loader.getSupportedExtensions().isEmpty()) {returnloader.getAdaptiveExtension(); }}return null; }}Copy the code
GetAdaptiveExtension () calls ExtensionLoader
createAdaptiveExtensionClass
This might get a little convoluted, but let’s rearrange it:
Back to ServiceConfig: When a private static final Protocol Protocol = ExtensionLoader. GetExtensionLoader (Protocol. The class). GetAdaptiveExtension ();
When performing ExtensionLoader. GetExtensionLoader (Protocol. The class), the method into the constructor ExtensionLoader (class <? > type), where type = protocol. class, when executing the constructor objectFactory = (type == extensionFactory.class? null : ExtensionLoader. GetExtensionLoader (ExtensionFactory. Class). GetAdaptiveExtension ()), again enters the constructor, When type = extensionFactory.class, this makes sense. The normal extension class needs to be created. Extensionfact-ry also needs to be created, but note that it is the constructor of both classes
When the second constructor returns, the code goes to the first constructor, and when the first constructor returns, the code continues to execute getAdaptiveExtens-ion ()
Have been analyzed getAdaptiveExtension front, will not repeat here, want to talk here about createAdaptiveExtensionClass (), to paste the code here
privateClass<? > createAdaptiveExtensionClass() {// This method is very long with many details. I suggest you go directly to the official website. Details are not our purpose this time.
// But the result returned is exactly what we care about
String code = createAdaptiveExtensionClassCode();
ClassLoader classLoader = findClassLoader();
com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
return compiler.compile(code, classLoader);
}
Copy the code
CreateAdaptiveExtensionClassCode () is to create the extension of the class, we don’t see the inside of the details, but it returned
If Protocol is used as an example, return:
package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
public void destroy(a) {
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(a) {
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.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);
}
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);
returnextension.refer(arg0, arg1); }}Copy the code
If ProxyFactory is used as an example, return:
package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class ProxyFactory$Adaptive implements com.alibaba.dubbo.rpc.ProxyFactory {
public com.alibaba.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, com.alibaba.dubbo.common.URL arg2)
throws com.alibaba.dubbo.rpc.RpcException {
if (arg2 == null) {
throw new IllegalArgumentException("url == null");
}
com.alibaba.dubbo.common.URL url = arg2;
String extName = url.getParameter("proxy"."javassist");
if (extName == null) {
throw new IllegalStateException(
"Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" +
url.toString() + ") use keys([proxy])");
}
com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class)
.getExtension(extName);
return extension.getInvoker(arg0, arg1, arg2);
}
public java.lang.Object getProxy(com.alibaba.dubbo.rpc.Invoker arg0,
boolean arg1) 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.getParameter("proxy"."javassist");
if (extName == null) {
throw new IllegalStateException(
"Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" +
url.toString() + ") use keys([proxy])");
}
com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class)
.getExtension(extName);
return extension.getProxy(arg0, arg1);
}
public java.lang.Object getProxy(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.getParameter("proxy"."javassist");
if (extName == null) {
throw new IllegalStateException(
"Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" +
url.toString() + ") use keys([proxy])");
}
com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class)
.getExtension(extName);
returnextension.getProxy(arg0); }}Copy the code
If we compare the two classes to see what they have in common, a careful eye might notice that the logic of the methods is the same, except for the variables and variable types, which are classes that are concatenated with strings and then generated by JavAssist. Protocol Adaptive and ProxyFactoryAdaptive and ProxyFactoryAdaptive are essentially proxy classes.
Let’s focus on this line:
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class)
.getExtension(extName);
Copy the code
When an extension method is called, it is loaded according to runtime parameters.
The previous question has been answered: Protocol Protocol is an instance of Protocol$Adaptive. When protocol.export() is called, the agent class loads the implementation class and then calls export() of the implementation class. The whole process is called SPI Adaptive extension.
conclusion
In this article we introduced the fundamentals of SPI and Dubbo SPI and adaptive extension. From beginning to end, we focused on the overall framework. As for some details, we didn’t cover them here, because I think you can either look at the code, check out the debug, or check out the official website.
The Dubbo SPI adaptive extension is not that mysterious, and it may seem a bit confusing at first. The essence of the extension is that it generates proxy classes for each interface at runtime, and then loads the extension classes through Dubbo SPI, and the extension classes perform the real logic.