The words written in the front

I use Dubbo also have a few years, has not read Dubbo source code, now to read Dubbo source code, analyze a few core Dubbo, and write a Dubbo source topic to record the learning process, for your reference, write bad places, welcome beat brick topic is divided into the following several parts:

  • Dubbo source analysis (a) Dubbo extension point mechanism
  • Dubbo source code analysis (two) Dubbo service release Export
  • Dubbo source code analysis (three) Dubbo service reference Refer
  • Dubbo source code analysis (4) Dubbo call chain – consumer (cluster fault tolerance mechanism)
  • Dubbo source analysis (five) Dubbo call chain – server
  • Dubbo source code analysis (six) Dubbo communication encoding decoding mechanism
  • Dubbo framework design details (unfinished, to be continued)

PS: Read the source code before mastering the following basis

  1. The JDK SPI
  2. Java multithreading/thread pool basics
  3. Javasissit Foundation (Dynamic Compilation)
  4. Netty based
  5. Zookeeper foundation, zkClient client API
  6. Factory pattern, decorator pattern, template pattern, singleton pattern, dynamic proxy pattern
  7. Spring’s schema custom extension
  8. serialization

PS: Advice before reading the source code

  1. The code volume is very large, each place is related, slowly read, don’t worry, once not twice, twice not three times, there is always a time to understand
  2. Look at it with the problem in mind. What is the purpose of the code and what problem does it solve
  3. Read along a main line; code that does not affect the flow can be skipped

Dubbo’s extension point

Why read extension points first

The reason why I chose to start with the extension point mechanism of Dubbo is that the overall architectural design of Dubbo is realized through extension points. Only by understanding this content can I understand the code.

Dubbo extension point specification

  • If you want to extend the custom SPI, you can configure three directories in the resources directory: meta-INF /dubbo/ or meta-INF /services/ or meta-INF /dubbo/internal/
  • File name and consistent interface name, file content for key = = vaule form XXX com. Alibaba. XXX. XxxProtocol
  • Take a chestnut: an agreement that if we want to expand Dubbo in meta-inf/Dubbo/com. Alibaba. Dubbo. RPC. Protocol increase line inside the file extension: XXX = com. Alibaba. XXX. XxxProtocol in Dubbo configuration file < Dubbo: protocol name = “XXX” / >, so that you can implement custom Dubbo agreement

The difference between Dubbo’s extension point and THE JDK’s SPI

Dubbo’s Extension point makes some improvements on the JDK’s SPI idea:

  • The Dubbo design uses a large amount of global caching. All extensions are cached in cachedInstances. This object type is ConcurrentMap
    ,>
  • Dubbo extension points support the default implementation, for example, @spi (” Dubbo “) in Protocol defaults to DubboProtocol, The default extension point can be ExtensionLoader. GetExtensionLoader (Protocol. The class). GetDefaultExtension ()
  • Dubbo extension points can dynamically retrieve extension objects, such as: ExtensionLoader. GetExtensionLoader (Protocol. The class). GetExtension (extName) to get the extension object, I think it is Dubbo extension point design of very interesting place, very convenient, This method is used extensively in the code
  • Dubbo’s extension point provides AOP functionality by wrapping XxxxFilterWrapper XxxxListenerWrapper on the original SPI class in cachedWrapperClasses
  • The Dubbo extension point provides IOC functionality by injecting the required instances through constructors, which will be analyzed later in the source code
  • Read the source code

    • What is the purpose of Dubbo’s SPI? Gets the specified object we need
    • How do you get it? ExtensionLoader. GetExtension (String name) let’s start from this code, this code is to obtain an adaptive extension class, we see the whole process of execution of the code:
ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
Copy the code

The getExtensionLoader method passes in a Protocol. Let’s see what Protocol looks like

@SPI("dubbo") public interface Protocol {// Get the default port, used when no port is configured. int getDefaultPort(); // Expose remote services: <br> @adaptive <T> Exporter<T>export(Invoker<T> invoker) throws RpcException; @adaptive <T> Invoker<T> refer(Class<T>type, URL url) throws RpcException; <br> void destroy();Copy the code

This is a protocol interface. It has an @SPI annotation on the class, a default value of dubbo, and an @adaptive annotation on the method. What do these two annotations do? Let’s move on to the getExtensionLoader method:

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {...If the value is null, a new ExtensionLoader will be created
       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

Continue into the new ExtensionLoader(type) method:

private ExtensionLoader(Class
        type) {
       this.type = type;
       objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
   }
Copy the code

We’re going to assign type, we’re going to assign objectFactory, we’re going to pass in type Protcol, Continue to execute ExtensionLoader. GetExtensionLoader (ExtensionFactory. Class). GetAdaptiveExtension () method, here we are divided into two steps: step one: ExtensionLoader. GetExtensionLoader (ExtensionFactory. Class), In this code, we get an instance of ExtensionLoader. In this instance, objetFactory is null. The getAdaptiveExtension() method assigns a value to cachedAdaptiveInstance.

  public T getAdaptiveExtension(a) {
       Object instance = cachedAdaptiveInstance.get();
       if (instance == null) {
           if (createAdaptiveInstanceError == null) {
               synchronized (cachedAdaptiveInstance) {
                   instance = cachedAdaptiveInstance.get();
                   if (instance == null) {
                       try {
                           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); }}Copy the code

Double-checked locking cache, if not into createAdaptiveExtension () method, this method has two parts, one is injectExtension, one is getAdaptiveExtensionClass (). NewInstance ()

private T createAdaptiveExtension(a) {                                                                                            
 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

See first getAdaptiveExtensionClass (), get an adapter class extension point

privateClass<? > getAdaptiveExtensionClass() { getExtensionClasses();if(cachedAdaptiveClass ! =null) {                                                              
     return cachedAdaptiveClass;                                                                 
 }                                                                                               
 return cachedAdaptiveClass = createAdaptiveExtensionClass();                                    
}                                                                                                   
Copy the code

Here we go to the getExtensionClasses() method, double-check the lock judgment, and if not, continue

privateMap<String, Class<? >> getExtensionClasses() { Map<String, Class<? >> classes = cachedClasses.get();if (classes == null) {
         synchronized (cachedClasses) {
             classes = cachedClasses.get();
             if (classes == null) { classes = loadExtensionClasses(); cachedClasses.set(classes); }}}return classes;
 }
Copy the code

Go to the loadExtensionClasses() method

    // This method is already synchronized with the getExtensionClasses method.
    privateMap<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 =newHashMap<String, Class<? > > (); loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY); loadFile(extensionClasses, DUBBO_DIRECTORY); loadFile(extensionClasses, SERVICES_DIRECTORY);return extensionClasses;
    }
Copy the code

GetAnnotation (spi. class). This type is passed in when the ExtensionLoader is initialized. ExtensionFactory interface class has @spi annotation, but value is empty, then call loadFile method three times, respectively corresponding to the Dubbo extension point configuration file path, we can find ExtensionFactory corresponding file in the source code.

With loadFile, the final extensionClasses return SpringExtensionFactory and SpiExtensionFactory to cachedClasses. AdaptiveExtensionFactory is cached directly into cachedAdaptiveClass because of the @adaptive annotation on the class. We need to think, what’s the difference between @Adaptive annotation being placed on a class versus a method

private void loadFile(Map
       
        > extensionClasses, String dir)
       ,> {...// 1. Check whether the current class has Adaptive annotations on it, if so, directly assign to cachedAdaptiveClass
       if (clazz.isAnnotationPresent(Adaptive.class)) {
           if (cachedAdaptiveClass == null) {
               cachedAdaptiveClass = clazz;
           } else if(! cachedAdaptiveClass.equals(clazz)) {throw new IllegalStateException("More than 1 adaptive class found: "
                       + cachedAdaptiveClass.getClass().getName()
                       + ","+ clazz.getClass().getName()); }}else {
           //2. If there is no class annotation, then check that there is no constructor for type in this class. If there is, put calss into cachedWrapperClasses
           try{ clazz.getConstructor(type); Set<Class<? >> wrappers = cachedWrapperClasses;if (wrappers == null) {
                   cachedWrapperClasses = newConcurrentHashSet<Class<? > > (); wrappers = cachedWrapperClasses; } wrappers.add(clazz); }catch (NoSuchMethodException e) {
               //3. Check if there is a default constructor
               clazz.getConstructor();
               if (name == null || name.length() == 0) {
                   name = findAnnotationName(clazz);
                   if (name == null || name.length() == 0) {
                       if(clazz.getSimpleName().length() > type.getSimpleName().length() && clazz.getSimpleName().endsWith(type.getSimpleName()))  { name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase();
                       } else {
                           throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + url);
                       }
                   }
               }
               String[] names = NAME_SEPARATOR.split(name);
               if(names ! =null && names.length > 0) {
                   //4. Check whether the class has the @activate annotation, if so, add it to cachedattributes
                   Activate activate = clazz.getAnnotation(Activate.class);
                   if(activate ! =null) {
                       cachedActivates.put(names[0], activate);
                   }
                   for (String n : names) {
                       //5. cache calss into cachedNames
                       if(! cachedNames.containsKey(clazz)) { cachedNames.put(clazz, n); } Class<? > c = extensionClasses.get(n);if (c == null) {
                           extensionClasses.put(n, clazz);
                       } else if(c ! = clazz) {throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
                       }
                   }
               }
           }
       }
   }
   }
Copy the code

So far, we have been given a extensionClasses and cache to cachedClasses, return to getAdaptiveExtensionClass () method

private Class<? >getAdaptiveExtensionClass() {
       getExtensionClasses();
       if(cachedAdaptiveClass ! = null) {return cachedAdaptiveClass;
       }
       return cachedAdaptiveClass = createAdaptiveExtensionClass();
   }
Copy the code

If cachedAdaptiveClass is not empty, then it returns cachedAdaptiveClass, which we just saw in the loadFile() method, @Adaptive annotation is on the class, then it will be cached in cachedAdaptiveClass, CachedAdaptiveClass has a value for AdaptiveExtensionFactory, so we return AdaptiveExtensionFactory, createAdaptiveExtension(), We just walked through one part of the createAdaptiveExtension() method and injectExtension method. This method is not used in the type= extensionFactory.class process. Let’s leave injectExtension behind and then go back to getAdaptiveExtension and cache the instance AdaptiveExtensionFactory into cachedAdaptiveInstance. Continue back to the ExtensionLoader method

private ExtensionLoader(Class<? >type) {
       this.type = type;
       objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
   }
Copy the code

At this point, the objectFactory has a value, which is an AdaptiveExtensionFactory, which continues to return the getExtensionLoader method

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {···· //EXTENSION_LOADERS checks if there is anytype, the ConcurrentMap < Class <? >, ExtensionLoader<? >> 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

We cache the returned ExtensionLoader instance into EXTENSION_LOADERS with type=Protocol

ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
Copy the code

So far, we have performed ExtensionLoader. GetExtensionLoader (Protocol. The class), got ExtensionLoader instance, Proceed with the getAdaptiveExtension() method, which we analyzed above, and see how it differs from the type=ExtensionFactory method. First look at the com. Alibaba. Dubbo. What. RPC Protocol file extension point (this file is dispersed in the source code, can be in the jars of dubbo, jar package is merged)

Now let’s look at the data in memory

privateClass<? > createAdaptiveExtensionClass() {// Generate bytecode files
   String code = createAdaptiveExtensionClassCode(); 
   // Get the classloader
   ClassLoader classLoader = findClassLoader(); 
   com.alibaba.dubbo.common.compiler.Compiler compiler = 
   ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
   // Dynamic compilation
   return compiler.compile(code, classLoader);                         
}                                                                                                                                           
Copy the code

Run compiler.compile(code, classLoader) and look at the AdaptiveCompiler class

@Adaptive
public class AdaptiveCompiler implements Compiler {

   private static volatile String DEFAULT_COMPILER;

   public static void setDefaultCompiler(String compiler) {
       DEFAULT_COMPILER = compiler;
   }

   publicClass<? > compile(String code, ClassLoader classLoader) { Compiler compiler; ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class); String name = DEFAULT_COMPILER;// copy reference
       if(name ! =null && name.length() > 0) {
           compiler = loader.getExtension(name);
       } else {
           compiler = loader.getDefaultExtension();
       }
       returncompiler.compile(code, classLoader); }}Copy the code

The DEFAULT_COMPILER value is JavassistCompiler. Loader.getextension (name) is not used here. The result is an instance of JavassistCompiler. Final call JavassistCompiler.com running () method to get $Adpative Protocol,

Go back to the entry point of our original code

ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
Copy the code

The final result of this code is Protocol$Adpative, so let’s take this proxy class out and look at it

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(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(ex tName);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(ex tName);returnextension.refer(arg0, arg1); }}Copy the code

If the Protocol$Adpative. Export method is used, the export() method in the adapter agent class gets the extName from the URL, so Dubbo is url-driven. See the Protocol extension = (Protocol) ExtensionLoader. GetExtensionLoader (Protocol. The class). GetExtension (extName) this approach, If this method is familiar, let’s analyze the getExtension(String Name) method, assuming extName=dubbo

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();
                if (instance == null) { instance = createExtension(name); holder.set(instance); }}}return (T) instance;
    }
Copy the code

Go to the createExtension() method

private T createExtension(String name) {
        //1. Get ExtensionClasses by name, DubboProtocolClass<? > clazz = getExtensionClasses().get(name);if (clazz == null) {
            throw findException(name);
        }
        try {
            //2. Obtain the DubboProtocol instance
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            //3. Dubbo's IOC inversion control is to extract object assignments from SPI and Spring.injectExtension(instance); Set<Class<? >> wrapperClasses = cachedWrapperClasses;if(wrapperClasses ! =null && wrapperClasses.size() > 0) {
                for(Class<? > wrapperClass : wrapperClasses) {//4instance = 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

InjectExtension (instance) :

private T injectExtension(T instance) {
        try {
            if(objectFactory ! =null) {
                //1. Get all the methods
                for (Method method : instance.getClass().getMethods()) {
                    // Check if it is set
                    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) : "";
                            // Get the instance to inject from objectFactory
                            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

This method is where Dubbo completes dependency injection, and this completes the code analysis of Dubbo’s extension point mechanism.

conclusion

  • Why Adaptive? Adaptive is designed to identify fixed known classes and extend unknown classes
  • What is the difference between annotations on a class and annotations on a method?

    1. Annotation on the class: on behalf of the artificial implementation, the realization of a decorative class (design mode of the decorative mode), it is mainly used to fix the known class, currently the whole system only two, AdaptiveCompiler, AdaptiveExtensionFactory.
    • Why is AdaptiveCompiler a fixed known class? Because the entire framework only supports Javassist and JdkCompiler.
    • Why is the AdaptiveExtensionFactory class permanently known? Because the entire framework only supports two ObjFactories, one spi and the other Spring 2. Note on the method: it means to automatically generate and compile a dynamic Adpative class, which is mainly used for SPI. Because THE CLASSES of SPI are unfixed and unknown extension classes, the dynamic XXX$Adaptive class is designed. For example, the SPI class of Protocol has injVM dubbo Registry filter Listener and many other extension unknown classes. It designs the Protocol$Adaptive class. Through ExtensionLoader. GetExtensionLoader (Protocol. The class). GetExtension (spi); To extract the object