一 什麼是Extension 機制
Dubbo的類加載機制是模仿jdk的spi加載機制;
Jdk的SPI擴展加載機制:約定是當服務的提供者每增加一個接口的實現類時,需要在jar包的META-INF/service/目錄下同時創建一個以服務接口命名的具體實現類,該文件裏面就是保存的實現該接口的具體實現類。而當外部程序裝配這個模塊的時候,就能通過該jar包META-INF/services/裏的配置文件找到具體的實現類名,並裝載實例化,完成模塊的注入。 基於這樣一個約定就能很好的找到服務接口的實現類,而不需要再代碼裏制定。jdk提供服務實現查找的一個工具類:java.util.ServiceLoader。
比如:
jdbc:在jdbc4以前,開發人員還需要基於Class.forName("xxx")的方式來裝載驅動。jdbc4也基於spi的機制來發現驅動提供商了,可以通過META-INF/services/java.sql.Driver文件裏指定實現類的方式來暴露驅動提供者如下圖:
但是原始的jdk擴展點加載機制有些缺陷:
·JDK標準的SPI會一次性實例化擴展點所有實現,如果有擴展實現初始化很耗時,但如果沒用上也加載,會很浪費資源。
·如果擴展點加載失敗,連擴展點的名稱都拿不到了。比如:JDK標準的ScriptEngine,通過getName();獲取腳本類型的名稱,但如果RubyScriptEngine因爲所依賴的jruby.jar不存在,導致RubyScriptEngine類加載失敗,這個失敗原因被吃掉了,和ruby對應不起來,當用戶執行ruby腳本時,會報不支持ruby,而不是真正失敗的原因。
·Dubbo增加了對擴展點IoC和AOP的支持,一個擴展點可以直接setter注入其它擴展點。
理解Dubbo的SPI機制之前的幾個概念:
·擴展點:Dubbo作爲一個非常靈活的框架,並不會強制所有用戶必須使用Dubbo框架裏自帶的某些架構,比如註冊中心的話,dubbo提供了zookeeper和redis,而開發者可以根據自己的需要使用自定的註冊中心,針對這種可靈活替換的技術我們就稱之爲擴展點技術,類似擴展點在Dubbo中有很多,比如Protocol,Filter,LoadBlance,Cluster等等;
·Wrapper:Dubbo在加載某個接口的擴展類時候,如果發現某個實現中有一個拷貝構造函數,那麼該接口實現是就是該接口的包裝類,此時dubbo會在真正的實現類上包裝一層wrapper,比如ProtocolFilterWrapper中含有Protocol引用,及構造函數,所以加載filter配置時會返回wrapper類;即這個時候從ExtensionLoader中返回的實際擴展類是被Wrapper包裝的接口實現類。
@SPI 註解 :可以認爲是定義默認實現類;
比如 Protocol 接口中,定義默認協議時 dubbo;
@SPI("dubbo")
public interface Protocol {}
@Adaptive 註解:該註解打在接口方法上;調 ExtensionLoader.getAdaptiveExtension()獲取設配類,會先通過前面的過程生成 java 的源代碼,在通過編譯器編譯成 class 加載。但是 Compiler 的實現策略選擇也是通過 通過編譯器編譯成 class 文件那豈不是要死循環下去了嗎?
此時分析 ExtensionLoader.getAdaptiveExtension()函數,對於有實現類上去打了註解 @Adaptive的dubbo spi擴展機制,它獲取設配類不在通過前面過程生成設配類java源代碼, 而是在讀取擴展文件的時候遇到實現類打了註解@Adaptive 就把這個類作爲設配類緩存在 ExtensionLoader 中,調用是直接返回。
·擴展點自動激活:可以理解爲條件激活,比如Filter接口有很多擴展點實現類,當想簡化配置用到哪些過濾器時,可以@Activate自動激活,或者配置爲@Activate(“xxx”)條件配置激活。
二 ExtensionLoader過程詳解
在Dubbo的擴展點加載機制中,ExtensionLoader是整個SPI加載的核心,而在Dubbo中ExtensionLoader的調用一般如下:
private static final ExtensionLoader<Container> loader = ExtensionLoader.getExtensionLoader(Container.class);
Container:
@SPI("spring")
public interface Container {
/**
* start.
*/
void start();
/**
* stop.
*/
void stop();
}
基於以下啓動類的流程進行ExtensionLoader過程講解(這種加載機制dubbo用的很多,後面的proctol也用到,爲了條理清晰點,從啓動開始梳理):
public class DemoProvider {
public static void main(String[] args) {
com.alibaba.dubbo.container.Main.main(args);
}
}
main啓動類:
/*
* Copyright 1999-2011 Alibaba Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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);
//獲取容器類的擴展點加載器
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) {
}
}
}
}
}
1. 獲取擴展點加載器的方法
private static final ExtensionLoader<Container> loader = ExtensionLoader.getExtensionLoader(Container.class);
private static <T> boolean withExtensionAnnotation(Class<T> type) {
return type.isAnnotationPresent(SPI.class);
}
@SuppressWarnings("unchecked")
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
//1.判斷該class是否爲空
if (type == null)
throw new IllegalArgumentException("Extension type == null");
//2.判斷該class是否爲接口類型
if(!type.isInterface()) {
throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
}
//3.判斷該class是否是@SPI註解類型
if(!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type(" + type +
") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
}
//創建一個class對應的擴展點加載程序
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
//如果該loader爲null,則創建一個class對應的extensionloader加載器放入EXTENSION_LOADERS中,
// 並且返回loader
if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
private ExtensionLoader(Class<?> type) {
this.type = type;
// EXTENSION_LOADERS 靜態的線程安全map,每個class類對應一個extensionloader加載器
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
getAdaptiveExtension()::
@SuppressWarnings("unchecked")
public T getAdaptiveExtension() {
//獲取一個自適應緩存對象
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if(createAdaptiveInstanceError == null) {
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
//沒有就自行創建一個AdaptiveExtension對象
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;
}
createAdaptiveExtension():
@SuppressWarnings("unchecked")
private T createAdaptiveExtension() {
try {
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);
}
}
private Class<?> getAdaptiveExtensionClass() {
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
getExtensionClasses()
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;
}
injectExtension():
//爲生成的 instance 注入變量;
//其目標是搜索所有 set 開頭,同時只有一個入參的函數,執行該函數,對變量進行注入
private T injectExtension(T instance) {
try {
if (objectFactory != null) {
for (Method method : instance.getClass().getMethods()) {
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) : "";
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;
}
2.加載指定文件裏的key-value值 例如:spring=com.alibaba.dubbo.container.spring.SpringContainer
loadExtensionClasses()
// 此方法已經getExtensionClasses方法同步過。
private Map<String, Class<?>> loadExtensionClasses() {
//1.獲取註解爲@SPI的類
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if(defaultAnnotation != null) {
//2.獲取@SPI("")中的值
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<?>>();
//加載META-INF/dubbo/internal/下的文件內容
loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
//加載META-INF/dubbo/下的文件內容
loadFile(extensionClasses, DUBBO_DIRECTORY);
//加載META-INF/services/下的文件內容
loadFile(extensionClasses, SERVICES_DIRECTORY);
return extensionClasses;
}
loadFile():
private void loadFile(Map<String, Class<?>> extensionClasses, String dir) {
String fileName = dir + type.getName();
try {
//Enumeration(枚舉)接口的作用和Iterator類似,
//只提供了遍歷Vector和HashTable類型集合元素的功能,不支持元素的移除操作
Enumeration<java.net.URL> urls;
//創建一個ExtensionLoader加載器
ClassLoader classLoader = findClassLoader();
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
if (urls != null) {
//遍歷Enumeration
while (urls.hasMoreElements()) {
java.net.URL url = urls.nextElement();
try {
//讀取url文件中的內容
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);
//判定此 Class 對象所表示的類或接口與指定的 Class 參數所表示的類或接口是否相同,
//或是否是其超類或超接口。如果是則返回 true;否則返回 false。
//如果該 Class 表示一個基本類型,且指定的 Class 參數正是該 Class 對象,則該方法返回 true;否則返回 false。
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.");
}
//isAnnotationPresent判斷是否有@Adative
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) {
//獲得構造器(Constructor)對象並調用其newInstance()方法創建對象
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它有兩個設置過濾條件的字段,group,value 都是字符數組。用來指定這個擴展類在什麼條件下激活
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);
}
}
extensionClasses的結果爲:
{spring=class com.alibaba.dubbo.container.spring.SpringContainer, jetty=class com.alibaba.dubbo.container.jetty.JettyContainer, log4j=class com.alibaba.dubbo.container.log4j.Log4jContainer}
3.獲取dubbo.properties的屬性值
if (args == null || args.length == 0) {
//獲取加載器中指定爲dubbo.container的值
String config = ConfigUtils.getProperty(CONTAINER_KEY, loader.getDefaultExtensionName());
//將參數進行格式處理
args = Constants.COMMA_SPLIT_PATTERN.split(config);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public static String getProperty(String key, String defaultValue) {
//先從系統參數中尋找是否有dubbo.container的key
String value = System.getProperty(key);
if (value != null && value.length() > 0) {
return value;
}
//否則從自定義的properties中獲取
Properties properties = getProperties();
return replaceProperty(properties.getProperty(key, defaultValue), (Map)properties);
}
getProperties():
public static Properties getProperties() {
if (PROPERTIES == null) {
synchronized (ConfigUtils.class) {
if (PROPERTIES == null) {
//默認在項目路徑下去尋找dubbo.properties.file的進程變量值
String path = System.getProperty(Constants.DUBBO_PROPERTIES_KEY);
if (path == null || path.length() == 0) {
//尋找dubbo.properties.file的環境變量值
path = System.getenv(Constants.DUBBO_PROPERTIES_KEY);
if (path == null || path.length() == 0) {
//最後獲取默認的dubbo.properties裏的路徑
path = Constants.DEFAULT_DUBBO_PROPERTIES;
}
}
//加載該路徑下的屬性值
PROPERTIES = ConfigUtils.loadProperties(path, false, true);
}
}
}
return PROPERTIES;
}
loadProperties():
public static Properties loadProperties(String fileName, boolean allowMultiFile, boolean optional) {
Properties properties = new Properties();
if (fileName.startsWith("/")) {
try {
FileInputStream input = new FileInputStream(fileName);
try {
properties.load(input);
} finally {
input.close();
}
} catch (Throwable e) {
logger.warn("Failed to load " + fileName + " file from " + fileName + "(ingore this file): " + e.getMessage(), e);
}
return properties;
}
List<java.net.URL> list = new ArrayList<java.net.URL>();
try {
Enumeration<java.net.URL> urls = ClassHelper.getClassLoader().getResources(fileName);
list = new ArrayList<java.net.URL>();
while (urls.hasMoreElements()) {
list.add(urls.nextElement());
}
} catch (Throwable t) {
logger.warn("Fail to load " + fileName + " file: " + t.getMessage(), t);
}
if(list.size() == 0) {
if (! optional) {
logger.warn("No " + fileName + " found on the class path.");
}
return properties;
}
if(! allowMultiFile) {
if (list.size() > 1) {
String errMsg = String.format("only 1 %s file is expected, but %d dubbo.properties files found on class path: %s",
fileName, list.size(), list.toString());
logger.warn(errMsg);
// throw new IllegalStateException(errMsg); // see http://code.alibabatech.com/jira/browse/DUBBO-133
}
// fall back to use method getResourceAsStream
try {
properties.load(ClassHelper.getClassLoader().getResourceAsStream(fileName));
} catch (Throwable e) {
logger.warn("Failed to load " + fileName + " file from " + fileName + "(ingore this file): " + e.getMessage(), e);
}
return properties;
}
logger.info("load " + fileName + " properties file from " + list);
for(java.net.URL url : list) {
try {
Properties p = new Properties();
InputStream input = url.openStream();
if (input != null) {
try {
p.load(input);
properties.putAll(p);
} finally {
try {
input.close();
} catch (Throwable t) {}
}
}
} catch (Throwable e) {
logger.warn("Fail to load " + fileName + " file from " + url + "(ingore this file): " + e.getMessage(), e);
}
}
return properties;
}
此時properties的結果爲:
{dubbo.container=log4j,spring,
dubbo.service.loadbalance=roundrobin,
dubbo.registry.address=zookeeper://127.0.0.1:2181,
dubbo.application.owner=william,
dubbo.application.name=demo-provider,
dubbo.protocol.port=20880,
dubbo.protocol.name=dubbo}
4.添加dubbo.properties中指定的dubbo.container
final List<Container> containers = new ArrayList<Container>();
for (int i = 0; i < args.length; i ++) {
containers.add(loader.getExtension(args[i]));
}
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);獲取對應key的class,
{spring=class com.alibaba.dubbo.container.spring.SpringContainer, jetty=class com.alibaba.dubbo.container.jetty.JettyContainer, log4j=class com.alibaba.dubbo.container.log4j.Log4jContainer}
測試:如果我在後面添加一個logback的容器,dubbo.container=log4j,spring, logback,此時運行報錯
原因找不到該爲logback的容器。
小結:
由於從消費者啓動容器開始梳理源碼,有些細節性的還沒涉及到,比如 擴展點的 Wrapper 類,@Adaptive的註解在dubbo中具體的作用,在後續的源碼解析流程中會細說。