dubbo擴展點機制

spring是如何啓動容器的

常見的一種在本地使用main方法啓動spring的方法java

public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"META-INF/spring/dubbo-demo-provider.xml"});
        context.start();
        ...
        //System.in.read(); // 按任意鍵退出
        context.close();
    }

dubbo是如何啓動容器的

這個你們應該都知道,經過com.alibaba.dubbo.container.Main.main方法來啓動的。web

public class Main {

    //在dubbo.properties中配置, 以配置dubbo.container=log4j,spring爲例
    public static final String CONTAINER_KEY = "dubbo.container";

    public static final String SHUTDOWN_HOOK_KEY = "dubbo.shutdown.hook";

    private static final Logger logger = LoggerFactory.getLogger(Main.class);
    
    //整個dubbo,最早使用ExtensionLoader的地方
    private static final ExtensionLoader<Container> loader = ExtensionLoader.getExtensionLoader(Container.class);

    private static volatile boolean running = true;

    public static void main(String[] args) {
        try {
            //1. 從dubbo.properties裏面讀取dubbo.container這個配置;
            if (args == null || args.length == 0) {
                String config = ConfigUtils.getProperty(CONTAINER_KEY, loader.getDefaultExtensionName());
                args = Constants.COMMA_SPLIT_PATTERN.split(config);
            }
            //2. 使用Container接口的ExtensionLoader中獲取具體的Container實現類;
            final List<Container> containers = new ArrayList<Container>();
            //agrs中有兩個值 "log4j,spring"
            for (int i = 0; i < args.length; i++) {
                containers.add(loader.getExtension(args[i]));
            }
            logger.info("Use container type(" + Arrays.toString(args) + ") to run dubbo serivce.");

            if ("true".equals(System.getProperty(SHUTDOWN_HOOK_KEY))) {
                //5. 當主線程被外部終止時,會觸發 shutdownhook,執行Container的stop與close方法
                Runtime.getRuntime().addShutdownHook(new Thread() {
                    public void run() {
                        for (Container container : containers) {
                            try {
                                container.stop();
                                logger.info("Dubbo " + container.getClass().getSimpleName() + " stopped!");
                            } catch (Throwable t) {
                                logger.error(t.getMessage(), t);
                            }
                            synchronized (Main.class) {
                                running = false;
                                //6.通知下面的鎖操做,主線程正常走完代碼,並最終中止。
                                Main.class.notify();
                            }
                        }
                    }
                });
            }
            //3. 執行Container接口的start方法;
            for (Container container : containers) {
                container.start();
                logger.info("Dubbo " + container.getClass().getSimpleName() + " started!");
            }
            System.out.println(new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss]").format(new Date()) + " Dubbo service server started!");
        } catch (RuntimeException e) {
            e.printStackTrace();
            logger.error(e.getMessage(), e);
            System.exit(1);
        }
        //4. 用一個死循環,保留主線程;
        synchronized (Main.class) {
            while (running) {
                try {
                    Main.class.wait();
                } catch (Throwable e) {
                }
            }
        }
    }

}

dubbo容器的SPI功能實現

明確下面幾個概念spring

  1. 擴展接口 com.alibaba.dubbo.container.Container
  2. 擴展配置 dubbo.container = log4j,spring
  3. 擴展實現
  • com.alibaba.dubbo.container.log4j.Log4jContainer編程

    log4j的日誌初始工做,當多進程啓動時,作日誌隔離
  • com.alibaba.dubbo.container.logback.LogbackContainerapp

    logback的日誌初始工做
  • com.alibaba.dubbo.container.spring.SpringContaineride

    spring容器的啓動,使用spring容器來實現aop與ioc,**【這個配置,每每是必選的】**
  • com.alibaba.dubbo.container.jetty.JettyContainer函數

    啓動一個Servlet Web容器,提供了一個web頁面,作一些監控之類的時期,注意:在寫HttpResponse的時候,也是用SPI機制,不一樣的請

    求頁面通過PageServlet交個不一樣的PageHandler去實現url

  • com.alibaba.dubbo.monitor.simple.RegistryContainer

    咱們來想一個這樣的問題,上面是dubbo支持的容器,包括log4j、logback、spring、jetty、registry,那麼dubbo是如何經過配置的方式來實現容器的可擴展的呢?假如給你作你怎麼作呢?線程

  • spring的API(Application Programming Interface、應用編程接口)方式,接口多實現類的動態調動;
  • JDK標準的SPI(Service Provider Interface、)機制
    dubbo的擴展點加載機制是從JDK的spi機制增強而來。
    dubbo改進了JDK標準的SPI機制如下問題:
  • spring與JDK的SPI都會一次性實例化擴展點全部實現,若是有擴展實現初始化很耗時,但若是沒用上,也會加載。
  • JDK的SPI機制不支持Ioc與Aop功能,而dubbo中的擴展點能夠直接setter注入其餘擴展點。【這個一部分,下面會有涉及,咱們會在下一個文章中詳細描述】

擴展接口Container源碼

關鍵說明,日誌

  1. 必須帶有SPI註解
  2. 註解裏面的值,是默認實現,在ExtensionLoader源碼去細講。
/**
 * Container. (SPI, Singleton, ThreadSafe)
 *
 * @author william.liangf
 */
@SPI("spring")
public interface Container {

    /**
     * start.
     */
    void start();

    /**
     * stop.
     */
    void stop();

}

ExtensionLoader源碼

關鍵說明,

1.  ExtensionLoader有一個private的構造函數,並經過getExtensionLoader這個鏡頭方法返回實例,是一個單例工廠類。
2.  一個擴展接口對應一個ExtensionLoader實例,也就是說最終咱們加載了多少個擴展接口(注意是擴展接口,而不是擴展實現類),就多少個實例;
3.  關鍵static final變量,全部實例共享
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();

    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();
4.  全部的final變量,單個實例共享,每個擴展接口對應的ExtensionLoader都不同
//擴展接口名稱
    private final Class<?> type;
    //也是一個擴展接口,用於注入擴展接口中須要注入的類,實現dubbo的擴展點的自動注入
    private final ExtensionFactory objectFactory;

    private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>();

    private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();

    private final Map<String, Activate> cachedActivates = new ConcurrentHashMap<String, Activate>();
    private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();
    private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();
    private volatile Class<?> cachedAdaptiveClass = null;
    private String cachedDefaultName;
    private volatile Throwable createAdaptiveInstanceError;

    private Set<Class<?>> cachedWrapperClasses;

    private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<String, IllegalStateException>();

結合Main類的使用,講一下幾個核心方法

核心方法 -> ExtensionLoader.getExtensionLoader

得到ExtensionLoader實例

private static final ExtensionLoader<Container> loader = ExtensionLoader.**getExtensionLoader**(Container.class);

獲取ExtensionLoader實例

  • getExtensionLoader(Container.class)【將返回的實例放到EXTENSION_LOADERS變量中】

    • new ExtensionLoader<T>(type) 【初始化type與objectFactory變量,初始化objectFactory變量的時候有一點點的繞。假如這個接口不是ExtensionFactory,就須要初始化這樣的一個objectFactory,不然就須要,具體後面會將】

獲取ExtensionLoader實例結束

核心方法 -> ExtensionLoader.getExtension

得到擴展實現
注意此時已經拿到了擴展接口Container對應的那個ExtensionLoader實例了,在下面的處理中,基本都是更新這個實例的變量,而不多會更新類變量了。

for (int i = 0; i < args.length; i++) {
   containers.add(loader.getExtension(args[i]));
}
  • getExtension("log4j" or "spring" or "logback" ....)

    • createExtension("log4j" or "spring" or "logback" ....) --建立指定類型的擴展接口的instance

      • getExtensionClasses() --加載擴展接口的全部class文件

        • loadExtensionClasses() --擴展接口的全部的class文件

          • loadFile() --從三個路徑下,查找class文件
      • clazz.newInstance() --建立指定class的instance
      • injectExtension(instace) --注入屬性Ioc

        • objectFactory.getExtension(pt, property) --反射的方式,解析setXxx(Xxx xxx)方法,注入Xxx實例
      • injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); --對實例進行層層包裝,最終返回一個包裝事後的instance

上面整體邏輯就是
圖片
具體介紹一下loadFile方法

//...
    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/";
    //...
    Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
    loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
    loadFile(extensionClasses, DUBBO_DIRECTORY);
    loadFile(extensionClasses, SERVICES_DIRECTORY);
    return extensionClasses;
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 {
    try {
        clazz.getConstructor(type);
        Set<Class<?>> wrappers = cachedWrapperClasses;
        if (wrappers == null) {
            cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
            wrappers = cachedWrapperClasses;
        }
        wrappers.add(clazz);
    } catch (NoSuchMethodException e) {
        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) {
            Activate activate = clazz.getAnnotation(Activate.class);
            if (activate != null) {
                cachedActivates.put(names[0], activate);
            }
            for (String n : names) {
                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());
                }
            }
        }
    }
}

從上面三個路徑下加載dubbo擴展點的配置。咱們以DUBBO_INTERNAL_DIRECTORY路徑下的配置文件爲例,說明下dubbo下擴展的配置。

  1. 擴展接口實現類,實現Container接口,例如SpringContainer.java
  2. 在資源META-INF.dubbo.internal文件夾下,有一個以Container接口全路徑名稱爲名字的文件;
  3. 上述文件名中內容格式爲 {key}={value},key爲擴展點實現類的配置名稱,例如spring、log4j等;value爲SpringContainer類的全路徑名稱

loadFile中就是以這樣的規則,解析這樣的配置文件,並放到extensionClasses這樣的Map中返回,extensionClasses的key是這個{key},value是這個{value}對應的class。

這裏面主要是四個邏輯,涉及到幾種狀況。
圖片

拿到全部配置的Container實例

for (Container container : containers) {
    container.start();
    logger.info("Dubbo " + container.getClass().getSimpleName() + " started!");
}

執行SpringContainer.java的start方法

public void start() {
        String configPath = ConfigUtils.getProperty(SPRING_CONFIG);
        if (configPath == null || configPath.length() == 0) {
            configPath = DEFAULT_SPRING_CONFIG;
        }
        context = new ClassPathXmlApplicationContext(configPath.split("[,\\s]+"));
        context.start();
    }
這個不是這篇文章最開始的那個問題的答案嘛,原來dubbo就是經過這麼簡單的方式的來啓動spring容器的。這算是一個首尾呼應嘛~

終於,終於,第一篇文章寫完了~ 下篇文章會講解擴展點是如何IOC的。
相關文章
相關標籤/搜索