开发者社区> java填坑路> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

Dubbo的SPI实现以及与JDK实现的区别

简介: 在 Java 里, 为了规范开发,制定了大量的「规范」与「标准」,这些上层的内容,大多是以接口的形式提供出来。那这些接口最终实现是谁呢,在哪里呢? 规范并不关心这个。
+关注继续查看

在 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文档这样描述

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

本群提供免费的学习指导 架构资料 以及免费的解答

不懂得问题都可以在本群提出来 之后还会有职业生涯规划以及面试指导

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
dubbo框架
dubbo框架
420 0
dubbo java 发布订阅(非spring配置)
  dubbo java  发布订阅(非spring配置)     发布  service ApplicationConfig config=new ApplicationConfig("springboot-dubbo-productor"); ...
1844 0
动态加载 dubbo spring
动态加载 dubbo spring   1.首先删除 配置文件中 dubboservice ,以及 项目中引用 service,然后 删除entity 中user,以及service 中userservice         2.
870 0
dubbo
随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进 单一应用架构 当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。
1392 0
最近项目用到Dubbo框架,临时抱佛脚分享一下共探讨(转)
1. Dubbo是什么? Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。简单的说,dubbo就是个服务框架,如果没有分布式的需求,其实是不需要用的,只有在分布式的时候,才有dubbo这样的分布式服务框架的需求,并且本质上是个服务调用的东东,说白了就是个远程服务调用的分布式框架(告别Web Service模式中的WSdl,以服务者与消费者的方式在dubbo上注册)其核心部分包含:1. 远程通讯: 提供对多种基于长连接的NIO框架抽象封装,包括多种线程模型,序列化,以及“请求-响应”模式的信息交换方式。
1021 0
dblink的介绍及常用管理脚本
一、database link概述 1、 database link是定义一个数据库到另一个数据库的路径的对象,在分布式的系统中一个数据库不可能包含所有的数据信息,有些数据信息是存放在其他的数据库里面的,因此通过dblink就可以实现从其他数据库获取数据的功能。
1018 0
407
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载
冬季实战营第三期:MySQL数据库进阶实战
立即下载