背景
分析dubbo之前得想聊聊spi了,spi的实现我认为主要是设计和实现是不同的两波了,这两波人为了减少彼此言语上的沟通,就通过接口的形式进行约束和实现,java自带有ServiceLoader,我们项目中的mysql 就是实现了jdk提供的spi,com.mysql.jdbc.Driver 实现了java 的 java.sql.Driver类
自带spi好处有很多,这里我们罗列下它设计还不够合理的地方:1 非线程安全,2 不能按需查找,3 不能按需加载,4 异常错误不明确;下面我们看看dubbo是怎么规避这些问题,然后是怎么加入更多新特性的。
案例介绍
我们先来跑个demo
package com.poizon.study.provider.spi;
@SPI
public interface Job {
void play();
}
package com.poizon.study.provider.spi;
public class ProgramImpl implements Job {
public ProgramImpl() {
System.out.println("init");
}
@Override
public void play() {
System.out.println("run..........");
}
}
文件位置:/resource/META-INF/dubbo/com.poizon.study.provider.spi.Job
program=com.poizon.study.provider.spi.ProgramImpl
public static void main(String[] args) {
ExtensionLoader<Job> extensionLoader = ExtensionLoader.getExtensionLoader(Job.class);
Job program = extensionLoader.getExtension("program");
program.play();
program = extensionLoader.getExtension("program");
program.play();
//init
//run..........
//run..........
}
代码运行起来,程序找到了Job 接口对应的ProgramImpl 实现,并且调用成功
思考?
-
是单例吗?
-
实现类是怎么创建的?反射?
-
有2个同样的配置文件会用哪个?
带着这些问题我们一探究竟
源码导读
点开 ExtensionLoader 类,先看类中的静态变量
public class ExtensionLoader<T> {
private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class);
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 = "META-INF/dubbo/internal/";
private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>();
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();
静态变量有三个明文字符串,其中有一个META-INF/dubbo/是我们刚才 com.poizon.study.provider.spi.Job 文件所在的目录,我们修完我们修改文件目录,执行main程序,类还是会初始化,dubbo这边扩展了,实现类的存储目录。
EXTENSION_LOADERS,EXTENSION_INSTANCES 是2个ConcurrentHashMap,这里就体现出了线程安全
接着看看 getExtensionLoader() 方法的实现
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
//删除非重点代码...
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;
}
//这里创建了 ExtensionLoader 对象,key 为Job.class 并且用map 做了缓存
接着我们看看getExtension() 方法,这可以重头戏
//code:A
public T getExtension(String name) {
if ("true".equals(name)) {
return getDefaultExtension();
}
final Holder<Object> holder = getOrCreateHolder(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;
}
我们跳过边角料,进到getOrCreateHolder() 看完;回来这边有个经典的单例模式,如上"if (instance == null)" 双重检查锁。我们接下去分析createExtension();
//code:B
final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
private Holder<Object> getOrCreateHolder(String name) {
//从ConcurrentHashMap中寻找,第一次为空,创建Holder对象
//改对象持有名字为name 的 Job 接口实现类
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<>());
holder = cachedInstances.get(name);
}
return holder;
}
//code:C
private T createExtension(String name) {
Class<?> clazz = getExtensionClasses().get(name);//看代码D处分析
//从实例缓存map中获取Job 的 ProgramImpl实现类
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
//没有则调用默认构造器反射创建实现类,
//所以亲测我们的实现类一定要有默认构造器,不然初始化会报错
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
//☆这边后面分析
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
//返回实现类
return instance;
}
最后返回的instance 就是 ProgramImpl 实现类
//代码:D
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;
}
private Map<String, Class<?>> loadExtensionClasses() {
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
//这里可以大胆猜测下,这6个方法因为是为了加载所在目录下面的文件
//之所以6个是apache 的dubbo 为了兼容 alibaba 的dubbo.
loadDirectory(extensionClasses, "META-INF/dubbo/internal", type.getName());
loadDirectory(extensionClasses, "META-INF/dubbo/internal", type.getName().replace("org.apache", "com.alibaba"));
loadDirectory(extensionClasses, "META-INF/dubbo/", type.getName());
loadDirectory(extensionClasses, "META-INF/dubbo/", type.getName().replace("org.apache", "com.alibaba"));
loadDirectory(extensionClasses, "META-INF/services/", type.getName());
loadDirectory(extensionClasses, "META-INF/services/", type.getName().replace("org.apache", "com.alibaba"));
return extensionClasses;
}
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
String fileName = dir + type;
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 resourceURL = urls.nextElement();
//加载完资源进行解析
loadResource(extensionClasses, classLoader, resourceURL);
}
}
}
//解析文件类
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
String line;
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) {
String name = null;
int i = line.indexOf('=');//也是采用key=value形式
if (i > 0) {
name = line.substring(0, i).trim();
line = line.substring(i + 1).trim();
}
if (line.length() > 0) {
//line 为 com.poizon.study.provider.spi.ProgramImpl
loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
}
}
}
}
}
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
//clazz 为 com.poizon.study.provider.spi.ProgramImpl
if (!type.isAssignableFrom(clazz)) {//ProgramImpl 必须实现Job 接口
throw new IllegalStateException("Error occurred when loading extension class (interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + " is not subtype of interface.");
}
if (clazz.isAnnotationPresent(Adaptive.class)) {//☆先跳过后面分析
cacheAdaptiveClass(clazz);
} else if (isWrapperClass(clazz)) {//☆先跳过后面分析
cacheWrapperClass(clazz);
} else {
clazz.getConstructor();//探测能否获取构造器
if (StringUtils.isEmpty(name)) {//key检查
name = findAnnotationName(clazz);
if (name.length() == 0) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
}
//key 支持多个可以这样写program1, program2, program3=com.xxx..Impl
String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names)) {
cacheActivateClass(clazz, names[0]);
for (String n : names) {
cacheName(clazz, n);
//将解析的key=program 和 value =com.poizon.study.provider.spi.ProgramImpl
//放到hashmap中,这边下面会分析,可以回答多个相同key怎么解决的问题
saveInExtensionClass(extensionClasses, clazz, n);
}
}
}
}
//这里很简单并没有什么order顺序,全部放到map中,先到先得,后面遇到就抛弃报错
//这边错误有点意思是最后全部执行完如果还没有的话才会统一抛出,而不是立即报错
//所以这边可以理解为Duplicate extension "这个错误永远也不会抛出
private void saveInExtensionClass(Map<String, Class<?>> extensionClasses, Class<?> clazz, String name) {
Class<?> c = extensionClasses.get(name);
if (c == null) {
extensionClasses.put(name, clazz);
} else if (c != clazz) {
throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + name + " on " + c.getName() + " and " + clazz.getName());
}
}
//这边看完 回到上面code:D 继续
扩展
当然dubbo 的spi 可不是简单的创建一个实现类,刚才我们在分析源码的时候,有些我做了☆标记留到了后面分析,我们再来回顾下
if (clazz.isAnnotationPresent(Adaptive.class)) {//☆先跳过后面分析
cacheAdaptiveClass(clazz);
} else if (isWrapperClass(clazz)) {//☆先跳过后面分析
cacheWrapperClass(clazz);
} else {
===================================================
//☆这边后面分析
injectExtension(instance);
网友评论