詳見我在《Java的SPI機制分析》文章中關於Dubbo的SPI機制的介紹,在此再也不贅述。java
下面以Container加載的過程爲例,來講明SPI擴展的實現流程:redis
全部加上@SPI註解的擴展點能夠有不一樣的擴展,Container代碼以下:spring
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這個接口有不少的實現版本,以下:api
可是具體哪一個種類的Container使用哪一個具體實現類還須要經過spi中的配置來指定。例如:緩存
dubbo/dubbo-container/dubbo-container-spring/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.container.Container文件中指定:app
spring=com.alibaba.dubbo.container.spring.SpringContainer
則當須要獲取spring的Extension的時候,最終會匹配到SpringContaineride
下面說明詳細的流程:測試類: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這個類:測試
代碼以下:ui
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
接下來經過反射將該類實例化出來並返回,至此,一個完整的搜索與實例建立完成了。zongjie
本篇以一個Container的加載流程講解了一個沒有Adaptive 和Wrapper的擴展,《Dubbo擴展點機制分析(二)》http://www.javashuo.com/article/p-pllwnnkv-bt.html 會詳細介紹ExtensionLoader及Adaptive的相關擴展實現。
參考文獻:
一、參考:http://blog.csdn.net/jdluojing/article/details/44947221
http://www.tuicool.com/articles/FR7NnyQ Dubbo源碼學習之ExtentionLoader
http://blog.csdn.net/jdluojing/article/details/44947221