Dubbo擴充套件點機制分析
一、擴充套件點配置
詳見我在《Java的SPI機制分析》文章中關於Dubbo的SPI機制的介紹,在此不再贅述。
二、擴充套件點流程分析之SPI
下面以Container載入的過程為例,來說明SPI擴充套件的實現流程:
所有加上@SPI註解的擴充套件點可以有不同的擴充套件,Container程式碼如下:
package com.alibaba.dubbo.container;
import com.alibaba.dubbo.common.extension.SPI;
/**
* Container. (SPI, Singleton, ThreadSafe)
*
* @author william.liangf
*/
@SPI("spring")
public interface Container {
/**
* start.
*/
void start();
/**
* stop.
*/
void stop();
}
Container這個介面有很多的實現版本,如下:
但是具體哪個種類的Container使用哪個具體實現類還需要通過spi中的配置來指定。例如:
dubbo/dubbo-container/dubbo-container-spring/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.container.Container檔案中指定:
spring=com.alibaba.dubbo.container.spring.SpringContainer
則當需要獲取spring的Extension的時候,最終會匹配到SpringContainer
下面說明詳細的流程:測試類:dubbo/dubbo-demo/dubbo-demo-provider/src/test/java/com/alibaba/dubbo/demo/provider/DemoProvider.java程式碼如下:
package com.alibaba.dubbo.demo.provider;
public class DemoProvider {
public static void main(String[] args) {
com.alibaba.dubbo.container.Main.main(args);
}
}
會調起:dubbo/dubbo-container/dubbo-container-api/src/main/java/com/alibaba/dubbo/container/Main.java這個類:
程式碼如下:
package com.alibaba.dubbo.container;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
import com.alibaba.dubbo.common.logger.Logger;
import com.alibaba.dubbo.common.logger.LoggerFactory;
import com.alibaba.dubbo.common.utils.ConfigUtils;
/**
* Main. (API, Static, ThreadSafe)
*
* @author william.liangf
*/
public class Main {
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);
/**
* Add by Jason
* ExtensionLoader是一個單例工廠類,它對外暴露getExtensionLoader靜態方法返回一個ExtensionLoader實體,
* 這個方法的入參是一個Class型別,這個方法的意思是返回某個介面的ExtensionLoader,對於一個介面,只會有一個
* ExtensionLoader實體。
* ExtensionLoader實體對外暴露一些介面來獲取擴充套件實現,這些介面分為幾類,分別是
* Activate Extension,Adaptive Extension,get extension by name ,supported extension
*/
private static final ExtensionLoader<Container> loader = ExtensionLoader.getExtensionLoader(Container.class);
private static volatile boolean running = true;
public static void main(String[] args) {
try {
if (args == null || args.length == 0) {
String config = ConfigUtils.getProperty(CONTAINER_KEY, loader.getDefaultExtensionName());
args = Constants.COMMA_SPLIT_PATTERN.split(config);
}
final List<Container> containers = new ArrayList<Container>();
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))) {
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;
Main.class.notify();
}
}
}
});
}
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);
}
synchronized (Main.class) {
while (running) {
try {
Main.class.wait();
} catch (Throwable e) {
}
}
}
}
}
String config = ConfigUtils.getProperty(CONTAINER_KEY, loader.getDefaultExtensionName());
會讀取相關的配置去去獲取Container的具體實現:
CONTAINER_KEY = dubbo.container
@SuppressWarnings({ "unchecked", "rawtypes" })
public static String getProperty(String key, String defaultValue) {
String value = System.getProperty(key);
if (value != null && value.length() > 0) {
return value;
}
Properties properties = getProperties();
return replaceProperty(properties.getProperty(key, defaultValue), (Map)properties);
}
先從系統配置中讀取是否有dubbo.container這個配置,沒有則繼續搜尋:
public static Properties getProperties() {
if (PROPERTIES == null) {
synchronized (ConfigUtils.class) {
if (PROPERTIES == null) {
String path = System.getProperty(Constants.DUBBO_PROPERTIES_KEY);
if (path == null || path.length() == 0) {
path = System.getenv(Constants.DUBBO_PROPERTIES_KEY);
if (path == null || path.length() == 0) {
path = Constants.DEFAULT_DUBBO_PROPERTIES;
}
}
PROPERTIES = ConfigUtils.loadProperties(path, false, true);
}
}
}
return PROPERTIES;
}
如果都沒有則會去一個預設的路徑去獲取配置檔案,如下:
public static final String DEFAULT_DUBBO_PROPERTIES = "dubbo.properties";
該路徑在classpath下面,路徑為:dubbo/dubbo-demo/dubbo-demo-provider/src/test/resources/dubbo.properties
具體內容為:
##
dubbo.container=log4j,spring
dubbo.application.name=demo-provider
dubbo.application.owner=william
#dubbo.registry.address=multicast://224.5.6.7:1234
dubbo.registry.address=zookeeper://127.0.0.1:2181
#dubbo.registry.address=redis://127.0.0.1:6379
#dubbo.registry.address=dubbo://127.0.0.1:9090
#dubbo.monitor.protocol=registry
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
dubbo.service.loadbalance=roundrobin
#dubbo.log4j.file=logs/dubbo-demo-consumer.log
#dubbo.log4j.level=WARN
從中可以看到dubbo.cantainer配置了log4j和spring兩個key的容器實現。
接下來會將獲取到的配置log4j和spring 放入一個待啟動Container列表:
final List<Container> containers = new ArrayList<Container>();
for (int i = 0; i < args.length; i ++) {
containers.add(loader.getExtension(args[i]));
}
通過loader.getExtension()方法獲取具體的key對應的例項。我們以args[1](即key為spring)為例:
/**
* 返回指定名字的擴充套件。如果指定名字的擴充套件不存在,則拋異常 {@link IllegalStateException}.
*
* @param name
* @return
*/
@SuppressWarnings("unchecked")
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;
}
繼續createExtension(),如下:
private T createExtension(String name) {
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);
}
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && wrapperClasses.size() > 0) {
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);
}
}
會呼叫:
private Map<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;
}
最終會呼叫:
// 此方法已經getExtensionClasses方法同步過。
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<?>>();
loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
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/";
這幾個路徑中去載入對應的配置檔案:
private void loadFile(Map<String, Class<?>> extensionClasses, String dir) {
String fileName = dir + type.getName();
try {
Enumeration<java.net.URL> urls;
ClassLoader classLoader = findClassLoader();
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
if (urls != null) {
while (urls.hasMoreElements()) {
java.net.URL url = urls.nextElement();
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));
try {
String line = null;
while ((line = reader.readLine()) != null) {
final int ci = line.indexOf('#');
if (ci >= 0) line = line.substring(0, ci);
line = line.trim();
if (line.length() > 0) {
try {
String name = null;
int i = line.indexOf('=');
if (i > 0) {
name = line.substring(0, i).trim();
line = line.substring(i + 1).trim();
}
if (line.length() > 0) {
Class<?> clazz = Class.forName(line, true, classLoader);
if (! type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error when load extension class(interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + "is not subtype of interface.");
}
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());
}
}
}
}
}
}
} catch (Throwable t) {
IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + url + ", cause: " + t.getMessage(), t);
exceptions.put(line, e);
}
}
} // end of while read lines
} finally {
reader.close();
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: " +
type + ", class file: " + url + ") in " + url, t);
}
} // end of while urls
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: " +
type + ", description file: " + fileName + ").", t);
}
}
對於不同型別的類,會做不同的處理,大致包括@Adaptive Wrapper等。這裡只說Container會走到的分支,後續會繼續詳細分析@Adaptive等相關的實現。
在搜尋的路徑dubbo/dubbo-container/dubbo-container-spring/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.container.Container 中這個配置檔案的內容為:
spring=com.alibaba.dubbo.container.spring.SpringContainer
會把從配置檔案中讀取的相關資訊讀取到快取extensionClasses中:
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());
}
}
最終,又回到這裡:
@SuppressWarnings("unchecked")
private T createExtension(String name) {
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);
}
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && wrapperClasses.size() > 0) {
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);
}
}
Class<?> clazz = getExtensionClasses().get(name);
得到具體的實現類為:com.alibaba.dubbo.container.spring.SpringContainer
接下來通過反射將該類例項化出來並返回,至此,一個完整的搜尋與例項建立完成了。
三、擴充套件點分析之ExtensionLoader
Dubbo的擴充套件點框架主要位於com.alibaba.dubbo.common.extension這個包下,其結構如下:
com.alibaba.dubbo.common.extension
|
|--factory
| |--AdaptiveExtensionFactory
| |--SpiExtensionFactory
|
|--support
| |--ActivateComparator
|
|--Activate #自動啟用載入擴充套件的註解
|--Adaptive #自適應擴充套件點的註解
|--ExtensionFactory #擴充套件點物件生成工廠介面
|--ExtensionLoader #擴充套件點載入器,擴充套件點的查詢,校驗,載入等核心邏輯的實現類
|--SPI #擴充套件點註解
Dubbo的擴充套件點主要基於ExtensionLoader這個單例類進行載入,在進行相關擴充套件時需注意擴充套件實現的執行緒安全性),其具有以下特性:
(1)擴充套件點自動包裝(Wrapper)
(2)擴充套件點自動裝配
(3)擴充套件點自適應(Adaptive)
(4)擴充套件點自動啟用(Active)
1、擴充套件點快取
相關引數快取在對應的資料結構中,先列出如下,方便後面看原始碼時查閱:
private final Concur