Dubbo的SPI实现以及与JDK实现的区别
在 Java 里, 为了规范开发,制定了大量的「规范」与「标准」,这些上层的内容,大多是以接口的形式提供出来。那这些接口最终实现是谁呢,在哪里呢?
规范并不关心这个。
所谓规范,是指定了一系列内容,来指导我们的开发实现。比方 Servlet规范对于 Servlet 的行为做了说明,具体实现时,可以是 Tomcat,可以是Jetty 等等。
再比方 Java 的 JDBC 规范,规定了 Driver 提供者需要实现的内容,但具体是 Oracle,或者者MySQL 都可以支持。关于JDBC 可以看之前一篇文章(没想到你是这样的 JDBC)。在之前我们可以通过 Class.forName来进行Driver 具体实现类的加载。从JDK1.6开始,官方提供了一个名为 「SPI」 的机制,来更方便快捷的进行对应实现类的加载,不需要我们关心。我们所需要做的,只要要将包含实现类的 JAR 文件放到 classpath中就可。
正好最近读了少量Dubbo的源码,其中有 Dubbo 的不同于JDK的另一种 SPI实现。所以这篇我们来看 Dubbo 的 「SPI」实现以及与 JDK 实现的区别。
首先,什么是 SPI 呢?
SPI(Service Provider Interfaces), 可以了解成一个交给第三方实现的API。JDK文档这样形容
A?service?is a well-known set of interfaces and (usually abstract) classes. A?service provider?is a specific implementation of a service.
在Java 中用到SPI的这些地方:
JDBC
JNDI
Java XML Processing API
Locael
NIO Channel Provider
……
通过这种SPI 的实现形式,我们的应使用仿佛有了可插拔的能力。
我们之前的文章Tomcat 中 的可插拔以及 SCI 的实现原理里,也分析了容器中是如何做到可插拔的。
JDK中的SPI 是怎么实现的呢?
在JDK中包含一个SPI最核心的类:ServiceLoader,在需要加载Provider类的时候,我们所要做的是:
ServiceLoader.load(Provider.class);
在JDK中规范了 Service Provider的路径,所有 Provider必需在JAR文件的META-INF/services目录下包含一个文件,文件名就是我们要实现的Service的名称全路径。比方我们熟习的JDBC 的MySQL实现, 在mysql-connector中,就有这样一个文件
META-INF/services/java.sql.Driver
这些provider是什么时候加载的呢?
因为Provider 的加载和初始化是Lazy的实现,所以需要的时候,可以遍历Provider 的 Iterator,按需要加载,已经加载的会存放到缓存中。
但有些实现不想Lazy,就直接在 ServiceLoader 的load执行之后直接把所有的实现都加载和初始化了,比方这次说的JDBC,所以这里在Tomcat里有个解决内存泄漏的,可以查看之前的文章(Tomcat与内存泄露解决)
继续说回具体的加载时机。我们一般在Spring 的配置中会添加一个datasource,这个数据源一般会在启动时做为一个Bean被初始化,此时数据源中配置的driver会被设置。
这些内容传入Bean中,会调使用DriverManager的初始化
static?{
? ?loadInitialDrivers();
?println(“JDBC DriverManager initialized”);
}
loadInitialDrivers 执行的的时候,除了ServiceLoader.load外,还进行了初始化
ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
Iterator driversIterator = loadedDrivers.iterator();
try{
?while(driversIterator.hasNext()) {
?driversIterator.next();
?}
}?catch(Throwable t) {
// Do nothing
}
return null;
我们再来看 Dubbo 的SPI实现方式。假如你能看下 Dubbo 的源码就会发现,实现时并没有用 JDK 的SPI,而是自已设计了一种。?
我们以Main class启动来看看具体的实现。
我们从用的入口处来看,第一步传入一个接口, 而后再传入期待的实现的名称
1SpringContainercontainer?=?(SpringContainer)?ExtensionLoader.getExtensionLoader(Container.class).getExtension(“spring”);
这里传入的是Container.class, 期待的实现是spring。
1//?synchronized?in?getExtensionClasses
2privateMap>?loadExtensionClasses()?{
3final?SPI?defaultAnnotation?=?type.getAnnotation(SPI.class);
4if(defaultAnnotation?!=null)?{
5Stringvalue?=?defaultAnnotation.value();
6if((value?=?value.trim()).length()?>0)?{
7String[]?names?=?NAME_SEPARATOR.split(value);
8if(names.length?>1)?{
9thrownewIllegalStateException(“more?than?1?default?extension?name?on?extension?”+?type.getName()
10+”:?”+?Arrays.toString(names));
11}
12if(names.length?==1)?cachedDefaultName?=?names[0];
13}
14}
15
16Map>?extensionClasses?=newHashMap>();
17loadDirectory(extensionClasses,?DUBBO_INTERNAL_DIRECTORY);
18loadDirectory(extensionClasses,?DUBBO_DIRECTORY);
19loadDirectory(extensionClasses,?SERVICES_DIRECTORY);
20returnextensionClasses;
21}
共从三个地方加载扩展的class
DUBBO_INTERNAL_DIRECTORY META-INF/dubbo/internal/
DUBBO_DIRECTORY META-INF/dubbo/
SERVICES_DIRECTORY META-INF/services/
1privatevoidloadDirectory(Map>?extensionClasses,Stringdir)?{
2StringfileName?=?dir?+?type.getName();
3try{
4Enumeration?urls;
5ClassLoader?classLoader?=?findClassLoader();
6if(classLoader?!=null)?{
7urls?=?classLoader.getResources(fileName);
8}else{
9urls?=?ClassLoader.getSystemResources(fileName);
10}
11if(urls?!=null)?{
12while(urls.hasMoreElements())?{
13java.net.URL?resourceURL?=?urls.nextElement();
14loadResource(extensionClasses,?classLoader,?resourceURL);
15}
16}
17}catch(Throwable?t)?{
18logger.error(“Exception?when?load?extension?class(interface:?”+
19type?+”,?description?file:?”+?fileName?+”).”,?t);
20}
21}
这里通过classLoader,寻觅符合传入的特定名称的文件,java.net.URL resourceURL = urls.nextElement();
此时会得到一个包含该文件的URLPath, 再通过loadResource,将资源加载
此时得到的文件内容是
spring=com.alibaba.dubbo.container.spring.SpringContainer
再进一步,将等号后面的class加载,就可完成。
loadClass时,并不是直接通过相似Class.forName等形式加载,而是下面这个样子:
1privatevoidloadClass(Map>?extensionClasses,?java.net.URL?resourceURL,?Class?clazz,Stringname)?throws?NoSuchMethodException?{
2if(!type.isAssignableFrom(clazz))?{
3thrownewIllegalStateException(“Error?when?load?extension?class(interface:?”+
4type?+”,?class?line:?”+?clazz.getName()?+”),?class?”
5+?clazz.getName()?+”is?not?subtype?of?interface.”);
6}
7if(clazz.isAnnotationPresent(Adaptive.class))?{
8if(cachedAdaptiveClass?==null)?{
9cachedAdaptiveClass?=?clazz;
10}elseif(!cachedAdaptiveClass.equals(clazz))?{
11thrownewIllegalStateException(“More?than?1?adaptive?class?found:?”
12+?cachedAdaptiveClass.getClass().getName()
13+”,?”+?clazz.getClass().getName());
14}
15}elseif(isWrapperClass(clazz))?{
16Set>?wrappers?=?cachedWrapperClasses;
17if(wrappers?==null)?{
18cachedWrapperClasses?=newConcurrentHashSet>();
19wrappers?=?cachedWrapperClasses;
20}
21wrappers.add(clazz);
22}else{
23clazz.getConstructor();
24if(name?==null||?name.length()?==0)?{
25name?=?findAnnotationName(clazz);
26if(name.length()?==0)?{
27thrownewIllegalStateException(“No?such?extension?name?for?the?class?”+?clazz.getName()?+”?in?the?config?”+?resourceURL);
28}
29}
30String[]?names?=?NAME_SEPARATOR.split(name);
31if(names?!=null&&?names.length?>0)?{
32Activate?activate?=?clazz.getAnnotation(Activate.class);
33if(activate?!=null)?{
34cachedActivates.put(names[0],?activate);
35}
36for(Stringn?:?names)?{
37if(!cachedNames.containsKey(clazz))?{
38cachedNames.put(clazz,?n);
39}
40Class?c?=?extensionClasses.get(n);
41if(c?==null)?{
42extensionClasses.put(n,?clazz);
43}elseif(c?!=?clazz)?{
44thrownewIllegalStateException(“Duplicate?extension?”+?type.getName()?+”?name?”+?n?+”?on?”+?c.getName()?+”?and?”+?clazz.getName());
45}
46}
47}
48}
49}
加载之后,需要对class进行初始化,此时直接newInstance一个,再通过反射注入的方式将对应的属性设置进去。
1privateTcreateExtension(String?name){
2Class?clazz?=?getExtensionClasses().get(name);
3if(clazz?==null)?{
4throwfindException(name);
5}
6try{
7T?instance?=?(T)?EXTENSION_INSTANCES.get(clazz);
8if(instance?==null)?{
9EXTENSION_INSTANCES.putIfAbsent(clazz,?clazz.newInstance());
10instance?=?(T)?EXTENSION_INSTANCES.get(clazz);
11}
12injectExtension(instance);
13Set>?wrapperClasses?=?cachedWrapperClasses;
14if(wrapperClasses?!=null&&?!wrapperClasses.isEmpty())?{
15for(Class?wrapperClass?:?wrapperClasses)?{
16instance?=?injectExtension((T)?wrapperClass.getConstructor(type).newInstance(instance));
17}
18}
19returninstance;
20}catch(Throwable?t)?{
21thrownewIllegalStateException(“Extension?instance(name:?”+?name?+”,?class:?”+
22type?+”)??could?not?be?instantiated:?”+?t.getMessage(),?t);
23}
24}
1privateTinjectExtension(T?instance){
2try{
3if(objectFactory?!=null)?{
4for(Method?method?:?instance.getClass().getMethods())?{
5if(method.getName().startsWith(“set”)
6&&?method.getParameterTypes().length?==1
7&&?Modifier.isPublic(method.getModifiers()))?{
8Class?pt?=?method.getParameterTypes()[0];
9try{
10String?property?=?method.getName().length()?>3??method.getName().substring(3,4).toLowerCase()?+?method.getName().substring(4)?:””;
11Objectobject=?objectFactory.getExtension(pt,?property);
12if(object!=null)?{
13method.invoke(instance,object);
14}
15}catch(Exception?e)?{
16logger.error(“fail?to?inject?via?method?”+?method.getName()
17+”?of?interface?”+?type.getName()?+”:?”+?e.getMessage(),?e);
18}
19}
20}
21}
22}catch(Exception?e)?{
23logger.error(e.getMessage(),?e);
24}
25returninstance;
26}
通过上面的形容我们看到,JDK 与 Dubbo的 SPI 实现上,尽管都是从JAR中加载对应的扩展,但还是有些显著的区别,比方:Dubbo 支持更多的加载路径,同时,并不是通过Iterator的形式,而是直接通过名称来定位具体的Provider,按需要加载,效率更高,同时支持Provider以相似IOC的形式提供等等。
欢迎工作一到五年的Java工程师朋友们加入Java架构开发:744677563
本群提供免费的学习指导 架构资料 以及免费的解答
不懂得问题都可以在本群提出来 之后还会有职业生涯规划以及面试指导
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » Dubbo的SPI实现以及与JDK实现的区别