dubbo擴充套件點機制
spring是如何啟動容器的
常見的一種在本地使用main方法啟動spring的方法
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方法來啟動的。
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功能實現
明確下面幾個概念
1. 擴充套件介面 com.alibaba.dubbo.container.Container
2. 擴充套件配置 dubbo.container = log4j,spring
3. 擴充套件實現
* com.alibaba.dubbo.container.log4j.Log4jContainer
log4j的日誌初始工作,當多程序啟動時,做日誌隔離
* com.alibaba.dubbo.container.logback.LogbackContainer
logback的日誌初始工作
* com.alibaba.dubbo.container.spring.SpringContainer
spring容器的啟動,使用spring容器來實現aop與ioc,【這個配置,往往是必選的】
* com.alibaba.dubbo.container.jetty.JettyContainer
啟動一個Servlet Web容器,提供了一個web頁面,做一些監控之類的時期,注意:在寫HttpResponse的時候,也是用SPI機制,不同的請
求頁面經過PageServlet交個不同的PageHandler去實現
* 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(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檔案
- loadExtensionClasses() –擴充套件介面的所有的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的。