Dubbo源码学习--环境搭建及基础准备(ServiceLoader、ExtensionLoader)
环境搭建
- Github上下载Dubbo最新发布版本,楼主下载版本为2.5.7。
- cd到源码解压目录,maven编译,命令为:
mvn clean install -Dmaven.test.skip
- 生成Intellij idea相关配置文件,命令为:
mvn idea:idea
- 双击运行生成的
dubbo-parent.ipr
文件
Java SPI
SPI是Service Provider Interfaces的简称,是Java中定义的一个很重要的规范,SPI使得应用之间变得更灵活、程序间更解耦。
一般在应用中会定义一个接口,具体的实现由对应的实现类去完成,即服务提供者(Service Provider)。模块与模块之间基于接口编程,模块之间不能对实现类进行硬编码、不能在代码里写具体的实现类,否则就违反了“可插拔原则”,如果要替换一种实现,就需要修改代码。此时,SPI提供了一种服务发现机制,完美解决了这个问题。
SPI机制基本思路是通过JDK提供的
java.util.ServiceLoader
类去主动发现服务,不需要硬编码具体的类。
当服务接口有多个实现类(即服务提供者)时,在jar包的META-INF/services/目录下创建一个以服务接口命名的文件,文件内容是该服务接口的具体实现类的全类名,一行记录是一个实现类的全类名。当外部程序装配这个模块时,通过jar包的META-INF/services/目录里的配置文件就可以找到具体的实现类名,从而进行实例化、完成模块的注入。
Java SPI 示例
定义服务接口:
package jdkspi;
public interface WorkerService {
void work();
}
该服务接口的两个实现类如下:
package jdkspi.impl;
import jdkspi.WorkerService;
public class WorkerServiceA implements WorkerService {
public void work() {
System.out.println("work hard ......");
}
}
package jdkspi.impl;
import jdkspi.WorkerService;
public class WorkerServiceB implements WorkerService {
public void work() {
System.out.println("work lazy ......");
}
}
在resources下新建目录META-INF/services/,在目录下新建文件。文件名为服务接口全名jdkspi.WorkerService
,具体内容如下
jdkspi.impl.WorkerServiceA //服务接口实现类全名
jdkspi.impl.WorkerServiceB //服务接口实现类全名
测试示例执行入口:
package jdkspi;
import java.util.Iterator;
import java.util.ServiceLoader;
public class Test {
public static void main(String[] args) {
ServiceLoader<WorkerService> serviceLoader = ServiceLoader.load(WorkerService.class);
WorkerService service = null;
Iterator<WorkerService> iterator = serviceLoader.iterator();
while (iterator.hasNext()) {
service = iterator.next();
service.work();
}
}
}
执行测试示例后,结果如下:
work hard ......
work lazy ......
Process finished with exit code 0
ServiceLoader源码分析
ServiceLoader是一个final类,不能被继承,实现了Iterable接口,可以遍历,如下:
public final class ServiceLoader<S> implements Iterable<S>
ServiceLoader属性如下:
private static final String PREFIX = "META-INF/services/";
// The class or interface representing the service being loaded
private final Class<S> service;
// The class loader used to locate, load, and instantiate providers
private final ClassLoader loader;
// The access control context taken when the ServiceLoader is created
private final AccessControlContext acc;
// Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// The current lazy-lookup iterator
private LazyIterator lookupIterator;
- PREFIX: 定义了配置文件的路径,是一个final类型常量,不能设置不能更改。表面Java SPI配置文件默认放在
META-INF/services/
路径下 - service: 定义服务接口类,final类型变量,一旦被赋值便不能修改,由load方法传入
- loader: 类加载器,一旦被赋值便不能修改
- acc: 访问控制上下文,一旦被赋值比昂不修改
- providers: 存储服务提供者,也即具体实现类。存储的顺序为配置文件中实现类的排列先后顺序
- lookupIterator: 迭代器,实现延迟加载的效果
ServiceLoader只有一个构造器,且是内部构造器。不能再外部直接通过new命令创建实例对象。如下:
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
ServiceLoader提供了三种静态类方法来创建实例对象。如下:
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) {
return new ServiceLoader<>(service, loader);
}
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {
ClassLoader cl = ClassLoader.getSystemClassLoader();
ClassLoader prev = null;
while (cl != null) {
prev = cl;
cl = cl.getParent();
}
return ServiceLoader.load(service, prev);
}
load(Class<S> service)
: 利用当前线程持有的ClassLoader创建实例load(Class<S> service, ClassLoader loader)
: 利用指定的ClassLoader创建实例loadInstalled(Class<S> service)
: 利用系统顶级ClassLoader创建实例
ServiceLoader提供iterator()方法用以生成迭代器。迭代器中方法内部具体由lookupIterator实现。如下:
public Iterator<S> iterator() {
return new Iterator<S>() {
Iterator<Map.Entry<String,S>> knownProviders
= providers.entrySet().iterator();
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
lookupIterator是LazyIterator的对象实例,LazyIterator是一个内部类,实现了Iterator接口,源码如下:
private class LazyIterator
implements Iterator<S>
{
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
public boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
public S next() {
if (acc == null) {
return nextService();
} else {
PrivilegedAction<S> action = new PrivilegedAction<S>() {
public S run() { return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
public void remove() {
throw new UnsupportedOperationException();
}
}
从上面源码中,不难发现:服务提供者的实例化过程是在具体调用时进行的,延迟加载。
Java SPI机制的ServiceLoader缺点:
- 每次获取一个实现类都必须遍历加载所有的实现类,即使是不想使用的实现类也加载了,造成了资源的浪费。
- 不能定向获取对应的实现类,必须iterator遍历查找,比较慢
Dubbo拓展机制
Dubbo拓展机制应用的就是Java SPI的思想。Java SPI配置文件中一条记录是一个实现类全名,但Dubbo配置文件中存储的是key-value键值对,value存储的是实现类全名。示例如下:
ls=com.alibaba.dubbo.rpc.protocol.dubbo.telnet.ListTelnetHandler
ps=com.alibaba.dubbo.rpc.protocol.dubbo.telnet.PortTelnetHandler
cd=com.alibaba.dubbo.rpc.protocol.dubbo.telnet.ChangeTelnetHandler
pwd=com.alibaba.dubbo.rpc.protocol.dubbo.telnet.CurrentTelnetHandler
invoke=com.alibaba.dubbo.rpc.protocol.dubbo.telnet.InvokeTelnetHandler
trace=com.alibaba.dubbo.rpc.protocol.dubbo.telnet.TraceTelnetHandler
count=com.alibaba.dubbo.rpc.protocol.dubbo.telnet.CountTelnetHandler
类似Java SPI机制的ServiceLoader,Dubbo中也有一个拓展加载器ExtensionLoader。ExtensionLoader中定义了配置文件的存储路径:
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/";
ExtensionLoader构造器也是内部构造器,在外部不能直接通过new命令来创建对象实例:
private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
同样,ExtensionLoader提供静态类方法getExtensionLoader来生成实例:
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
if (type == null)
throw new IllegalArgumentException("Extension type == null");
if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
}
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type(" + type +
") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
}
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
调用getExtension方法可以根据name值获取指定的拓展实现类,实例化后的拓展实现类以Holder类封装存储在cachedInstances中,cachedInstances是ConcurrentMap<String, Holder<Object>>
变量。
/**
* 返回指定名字的扩展。如果指定名字的扩展不存在,则抛异常 {@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;
}
getExtension方法中采用了double-check机制,拓展实现类的实例化是在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);
}
}
方法实现大体流程为:
- name作为key值,获取对应的class。在getExtensionClasses中是有做同步处理的
- 根据得到的class创建实例
- 对实例化对象进行依赖注入
- 对依赖注入后的实例化对象进行包装
依赖注入及包装源码如下:
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;
}
此即是Dubbo拓展机制的大体流程,跟Java SPI机制非常类似,可看作Java SPI机制的一个优化与拓展。下一节将探讨provider服务的发布过程
Dubbo源码学习--环境搭建及基础准备(ServiceLoader、ExtensionLoader)的更多相关文章
- Tomcat源码学习 - 环境搭建
一. 源码下载 PS: 多图预警 在开始阅读源码之前,我们需要先构建一个环境,这样才能便于我们对源码进行调试,具体源码我们可以到官网进行下载(这里我以8.5.63版本为例). 二. 项目导入 下载并解 ...
- Java之美[从菜鸟到高手演变]之Spring源码学习 - 环境搭建
准备工作 1.下载安装STS(Spring Tool Suite),在eclipse market里直接搜索.下载.安装.2.下载安装gradle, Spring源码使用gradle构建,下载后解压到 ...
- Hadoop学习笔记(10) ——搭建源码学习环境
Hadoop学习笔记(10) ——搭建源码学习环境 上一章中,我们对整个hadoop的目录及源码目录有了一个初步的了解,接下来计划深入学习一下这头神象作品了.但是看代码用什么,难不成gedit?,单步 ...
- 在IDEA中搭建Java源码学习环境并上传到GitHub上
打开IDEA新建一个项目 创建一个最简单的Java项目即可 在项目命名填写该项目的名称,我这里写的项目名为Java_Source_Study 点击Finished,然后在项目的src目录下新建源码文件 ...
- Dubbo源码学习--注册中心分析
相关文章: Dubbo源码学习--服务是如何发布的 Dubbo源码学习--服务是如何引用的 注册中心 关于注册中心,Dubbo提供了多个实现方式,有比较成熟的使用zookeeper 和 redis 的 ...
- Dubbo源码学习--优雅停机原理及在SpringBoot中遇到的问题
Dubbo源码学习--优雅停机原理及在SpringBoot中遇到的问题 相关文章: Dubbo源码学习文章目录 前言 主要是前一阵子换了工作,第一个任务就是解决目前团队在 Dubbo 停机时产生的问题 ...
- Hadoop源码阅读环境搭建(IDEA)
拿到一份Hadoop源码之后,经常关注的两件事情就是 1.怎么阅读?涉及IDEA和Eclipse工程搭建.IDEA搭建,选择源码,逐步导入即可:Eclipse可以选择后台生成工程,也可以选择IDE导入 ...
- Spark源码分析环境搭建
原创文章,转载请注明: 转载自http://www.cnblogs.com/tovin/p/3868718.html 本文主要分享一下如何构建Spark源码分析环境.以前主要使用eclipse来阅读源 ...
- 【转】Linux(ubuntu14.04)上编译Android4.4源码的环境搭建及编译全过程
原文网址:http://jileniao.net/linux-android-building.html sublime text让我伤心.本来很信任sublime text的自动保存功能,之前使用一 ...
随机推荐
- javascript(js)创建对象的模式与继承的几种方式
1.js创建对象的几种方式 工厂模式 为什么会产生工厂模式,原因是使用同一个接口创建很多对象,会产生大量的重复代码,为了解决这个问题,产生了工厂模式. function createPerson(na ...
- iOS开发注意事项(一)
1.OC的消息机制与C++等的函数(方法)有很大的不同,OC在运行时所执行的代码由运行环境来决定,而C++等则由编译器决定.如果调用的函数是多态的,C++在运行时要按照虚方法表来查出到底执行哪个函数, ...
- Jquery实现数据双向绑定(赋值和取值),类似AngularJS
<!DOCTYPE html> <html> <head> <meta name="viewport" content="wid ...
- OpenGL ES2.0贴图
1.定义传入着色器的顶点数据及索引 //传入结构体 typedef struct { ]; ]; } Vertex; //顶点数据 const Vertex Vertices[] = { {{, -, ...
- JavaScript--我发现,原来你是这样的JS(四)(看看变量,作用域,垃圾回收机制是啥)
一.介绍 这是红宝书(JavaScript高级程序设计 3版)的读书笔记第四篇,是红宝书第四章内容(主要是变量和作用域问题),当然其中还有我个人的理解.红宝书这本书可以说是难啃的,要看完不容易,挺厚的 ...
- 【持续更新】.Net 开发中给自己埋下的坑!
1.文件“XXX”正在由另一进程使用,因此该进程无法访问此文件. 原因剖析:文件在主线程操作,在子线程中读写操作文件,刚开始没有意识到程序的问题所在,总是在FileStream中报错,google后常 ...
- Fedora 下 Google-Chrome 经常出现僵尸进程的权宜办法
对于Chrome_ProcessL 和Chrome_FileThre这两僵尸进程,估计遇到过的人都对其各种无奈吧,放任不管吧,越来越多,然后卡死,只能另开个X环境或者在其他的TTY里干掉他俩再切回去, ...
- SpringMVC 配置
1.在WEB-INF\web.xml中定义前端控制器 <servlet> <servlet-name>springmvc</servlet-name> <se ...
- IIS6、7添加反向代理的步骤
1.安装requestRouter_amd64.msi和rewrite_x64_zh-CN.msi. 打包下载地址:http://files.cnblogs.com/files/wangwust/ii ...
- PHP备忘录
file_exists()在判断文件是否存在的时候是递归判断每个目录是不是有执行权限. Echo输出大字符串速度慢:打开apache配置项‘deflate’进行压缩输出.