JDK的SPI?
SPI是jdk5引进的新特性,说到dubbo的spi机制,就不得不提jdk自带的SPI,SPIdubbo的spi机制借鉴了jdk的spi.本质思想上是一样的.这里只对JDK做个简单的介绍.后面会写专门的内容进行介绍
JDK的SPI的一点介绍
SPI的全称是Services Provide Interface,简单来说就是一种服务发现机制.比较具体的定义可以查看官方文档,它的具体实现思路如下:
1.新建Spi的目标接口(例如我的接口为:com.json.blog.spi.ISpi)
2.自定义改接口的实现类.(com.json.blog.spi.SpiImpl1,com.json.blog.spi.SpiImpl2)
3.在classpath路径下新建META-INF/services文件夹.
4.在文件夹内新建文件,文件名接口的全路径为名称.(文件名:com.json.blog.spi.ISpi)
5.在文件内写入实现类的路径名+类名,多个实现类需要进行换行.(com.json.blog.spi.SpiImpl1 com.json.blog.spi.SpiImpl2).
使用场景
1.数据库驱动加载,JDBC等接口实现类的加载
2.日志面向接口实现类的加载
3.Spring等开源框架的大量使用
3.Dubbo使用其思想对其进行了改造
优点
实现了java代码的解耦
缺点
1.多个并发多线程使用ServiceLoader类的实例是不安全的.
2.虽然ServiceLoader也算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍.
3.破坏了java的双亲委派模型.
Dubbo的SPI
Dubbo的改进
1.原本jdk只读取META-INF/services目录下,而dubbo会多读取META-INF/dubbo/和META-INF/dubbo/internal/目录下.
2.dubbo只会获取SPI注解中key对应的class,然后产生实例,不像JDK.会实例化全部的class.降低了系统负担.
Dubbo的SPI源码分析
1.程序的入口为我自己性的一个测试类.Dubbo使用ExtensionLoader实现dubbo的SPI机制的加载.ExtensionLoader是私有的构造函数.只能通过静态函数getExtensionLoader()方法获得指定的接口的ExtensionLoader的实现类.然后通过getAdaptiveExtension()方法获得对应接口自适应扩展点.或者通过getExtension("name")获得名称为name的实现类.
public class extentionLoadTest {
public static void main(String[] args) throws IOException {
Protocol protocol = ExtensionLoader.
//根据一个类型<CLass>获得一个ExtentionLoading
getExtensionLoader(Protocol.class)
//获得一个自适应扩展点
.getAdaptiveExtension();
System.out.println(protocol.getDefaultPort());
}
}
2.为了防止重复加载,对于对应接口的ExtensionLoader在EXTENSION_LOADERS进行了缓存.如果在缓存中没有获取到ExtensionLoader,则对其进行实例化.获得ExtensionLoader对象之后将其放入到EXTENSION_LOADERS缓存中.下次需要相同接口的ExtensionLoader则直接从缓存中获取.
/**
* 静态类,通过传入的class,返回对应的实现类
*/
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { //T--->protocol
/******************************部分非空检查的代码已被删除*******************************/
//先从静态缓存中取对应ExtensionLoader.
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
//T的类型为protocol
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
3.ExtensionLoader的构造函数
/**
* 为什么此函数要私有构造函数?
*
* 自己的一点理解:SPI的思想在dubbo中使用十分广泛,如果不对其私有化,任意的实例化,对于系统的运行可能会造成沉重的负担.当期私有之后.需要对该类.
* 可以通过getExtensionLoader获取对应的实例.并且在其中加入的EXTENSION_LOADERS作为缓存.对每种类型的ExtensionLoader只有一个实例.防止多线程并
* 发,代码中大量使用的双重检查锁的代码,以确保代码的健壮性.为了确保双重检查锁有效.加锁的对象应该用volatile修饰
* @param type
*/
private ExtensionLoader(Class<?> type) {
this.type = type;
//首先创建ExtensionLoader的objectFactory对象
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
到此为止,Extendloader的实例化就已经完成,现在我们开始研究很眼熟的getAdaptiveExtension().他已经出现了两次.
4.在Extendloader实例化完成之后,便调用对应的getAdaptiveExtension()方法.获取默认的实现类.在这个过程中.dubbo根据@Adaptive注解.如果某个实现类有该注解.则返回该直接实现类.如果所有的实现类均没有该注解.则查找前头getExtensionLoader(T)中T接口含有@Adaptive注解的方法生成代理方法.如果没有方法拥有注解.则直接抛出异常
@SuppressWarnings("unchecked")
public T getAdaptiveExtension() {
//首先现从缓存中去取
Object instance = cachedAdaptiveInstance.get();
/**
* 使用双重检查锁提高效率并且防止高并发,第一个if提高性能,第二个if确保高并发 --double check
*/
if (instance == null) {
if (createAdaptiveInstanceError == null) {
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
//首次执行肯定为空.则开始创建适应类
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
}
}
}
} else {
throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
}
}
return (T) instance;
}
5.代码很简单,就是调用创建自适应类的实例然后注入属性,最后返回结果
/**
* 获得自适应的扩展点
*/
private T createAdaptiveExtension() {
try {
//ExtensionFactory
/**
* injectExtension:为生成对象的内容的进行注入属性
* getAdaptiveExtensionClass:获取到自适应的class.拥有@Adaptive的拥有非常高的优先级.直接返回该对象,
* 没有的话,动态生成class类.然后编译返回对应结果
* newInstance:反射生成对应的class实例
*/
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}
6.判断是否有@Adaptive注解过得类,如果有则直接返回实例class信息.如果没有则调用createAdaptiveExtensionClass()方法为该接口创建动态实现类
private Class<?> getAdaptiveExtensionClass() {
//T--->ExtensiveFactory/Protocol
getExtensionClasses();
//判断spi注解的实现类中有没有被@adaptive注解过.有的话直接返回.否则通过javasisst操作字节码进行动态创建的实现类
//cachedAdaptiveClass是在getExtensionClasses()方法中查找到被@Adaptive过得类,而且所有的实现类中只能有一个类被
//@Adaptive注解,否则也会报错
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
7.没什么说的,又是double check,可见在自己代码中可以为了提高代码质量,也可以使用这个思想
//加载扩展点的实现类
private Map<String, Class<?>> getExtensionClasses() {
//cachedAdaptiveClass怎么来的?
Map<String, Class<?>> classes = cachedClasses.get();
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
//加载系统所有的接口实现类
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}
8.jdk的SPI只是读取META-INF/Services文件下的内容,而Dubbo则加载多个路径下的内容.这是dubbo做的扩展之一.
/**
* 加载接口所有的实现类
* @return
*/
private Map<String, Class<?>> loadExtensionClasses() {
//cachedAdaptiveClass怎么来的?
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
//META-INF/dubbo/internal/
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
//META-INF/dubbo/internal/(由于该版本的代码alibaba捐献给apache孵化,全量替换,容错)
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
//META-INF/dubbo/
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
//META-INF/dubbo/(由于该版本的代码alibaba捐献给apache孵化,全量替换,容错)
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
//META-INF/services/
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
//META-INF/services/(由于该版本的代码alibaba捐献给apache孵化,全量替换,容错)
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
//返回所有加载到的class
return extensionClasses;
}
9.上面虽然调用6次loadDirectory()方法,但只是路径不同.但是每个方法内容是一样的.
/**
*
* @param extensionClasses
* @param dir META-INF/services/dubbo META-INF/dubbo/internal META-INF/service
* @param type classname
*/
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
String fileName = dir + type;
try {
Enumeration<java.net.URL> urls;
//获取类加载器
ClassLoader classLoader = findClassLoader();
//获取到完成的文件的文件路径
if (classLoader != null) {
//获取的文件全路径
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
if (urls != null) {
//遍历加载每个文件对应的class
while (urls.hasMoreElements()) {
java.net.URL resourceURL = urls.nextElement();
//加载每个文件的实现
loadResource(extensionClasses, classLoader, resourceURL);
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", description file: " + fileName + ").", t);
}
}
10.主要是对获取到的不同路径接口文件,然后解析接口文件里的内容,之后又调用loadClass()方法.加载该类.
/**
*
* @param extensionClasses SPI注解实现类的类名
* @param classLoader 类加载器
* @param resourceURL 文件的全路径
*/
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
try {
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) {
try {
String name = null;
//采用key=value方式进行分割
int i = line.indexOf('=');
if (i > 0) {
name = line.substring(0, i).trim();//SPI扩展中的key
line = line.substring(i + 1).trim();//SPI扩展中的value
}
if (line.length() > 0) {
//加载对应的class
loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
}
} catch (Throwable t) {
IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
exceptions.put(line, e);
}
}
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", class file: " + resourceURL + ") in " + resourceURL, t);
}
}
11.加载目录下接口文件中定义的class文件.并且对其做校验
/**
*
* @param extensionClasses
* @param resourceURL
* @param clazz
* @param name SPI对应的文件中的key
* @throws NoSuchMethodException
*/
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
/**
* 是否实现了当前加载的扩展点的实现
*/
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error occurred when loading extension class (interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + " is not subtype of interface.");
}
//判断是否有@Adaptive注解
if (clazz.isAnnotationPresent(Adaptive.class)) {
//有的话为cacheAdaptiveClass赋值.表示该Type有自定义的适配器类.不需要动态创建,直接返回该类
//为cachedAdaptiveClass赋值.如果有重复的值会进行报错处理.
cacheAdaptiveClass(clazz);
//判断构造参数是否合法
} else if (isWrapperClass(clazz)) {
cacheWrapperClass(clazz);
} else {
//动态创建对应的适配器
clazz.getConstructor();
//如果SPI文件的中的格式key为空,可以通过Extension为其赋值key,目前来看,这种情况很少
//我们可以不在文件中用key=value格式定义.直接写入类的全路径,然后在类中给Extension注解,赋值名称,效果相同
if (StringUtils.isEmpty(name)) {
name = findAnnotationName(clazz);
if (name.length() == 0) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
}
String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names)) {
cacheActivateClass(clazz, names[0]);
for (String n : names) {
cacheName(clazz, n);
saveInExtensionClass(extensionClasses, clazz, n);
}
}
}
}
PS:代码只是跟随主要的流程分支,部分分支可能被忽略.
12.文件的加载已经完成,此时在返回getAdaptiveExtensionClass()方法.如果cachedAdaptiveClass为空.则需要对接口中@adaptive注解的方法动态生成适配器.
/**
* 基于动态字节码创建适配器扩展点
* @return
*/
private Class<?> createAdaptiveExtensionClass() {
//生成的适配器扩展点代码
String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
//获得类加载器
ClassLoader classLoader = findClassLoader();
//编译对应的代码
org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
//返回编译后的class
return compiler.compile(code, classLoader);
}
13.此处只分析generate()方法.个人感觉虽然代码重构后整体看的比较舒服,但是真正读起来对细节把握容易错乱.
/**
* generate and return class code
*/
public String generate() {
// no need to generate adaptive class since there's no adaptive method found.
if (!hasAdaptiveMethod()) {
throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
}
StringBuilder code = new StringBuilder();
code.append(generatePackageInfo());
code.append(generateImports());
code.append(generateClassDeclaration());
Method[] methods = type.getMethods();
for (Method method : methods) {
code.append(generateMethod(method));
}
code.append("}");
if (logger.isDebugEnabled()) {
logger.debug(code.toString());
}
return code.toString();
}
整体而言,所有的方法都是为了拼出一个该接口的适配器代码.大部分比较简单.后文只对generateMethodContent()进行分析.私以为这是最核心的部分.
14.生成被@Adaptive注解的方法
/**
* generate method content
*/
/**
* 生成代码的逻辑:
* 1.判断是否有Adaptive方法,如果没有,则直接抛出异常,表示拒绝创建该适配器代码;
* 2.创建package,import,class代码;
* 3.遍历该接口所有的方法,为其生成代码
* 4.如果方法没有被Adaptive注解,抛出UnsupportedOperationException异常,该方法生成结束,
* 被Adaptive注解的方法执行第5步
* 5.获取到方法的参数,如果参数是URL,则继续执行第7步,否则执行第8步
* 6.如果不是URl,从参数中尝试获取URL.获取到,则继续向下执行,否者代码抛出异常.流程退出
* 7.获取到URL后.尝试获取URL,如果获取到,则继续向后执行,否则生成的代码中抛出异常.
* 8.获去到Adaptive注解中的参数,如果没有参数,则直接使用接口名称(转为小写)
* 9.判断是否Invocation参数.
* 10.根据adaptive注解的参数和Invocation形参,确定动态扩展点的key值来源,
* 分别有url.getMethodParameter()/url.getParameter()/url.getProtocol()
* 11.生成extendloader代码加载
* 12.生成返回值
*
*
* @param method
* @return
*/
private String generateMethodContent(Method method) {
Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
StringBuilder code = new StringBuilder(512);
if (adaptiveAnnotation == null) {
return generateUnsupported(method);
} else {
/**
* 此版本已经对方法进行了封装,不像alibaba版本易懂
* 生成的<br>代码</br>的逻辑
* 尝试从参数中中获得URL的对象,如果参数值URL那么完美,直接使用.否则反射遍历参数的所有方法.判断是否有getUrl方法.有的话生
* 成XXX.getURl();获取到url.如果获取不到.反射报错
*/
int urlTypeIndex = getUrlTypeIndex(method);
// found parameter in URL type
//urlTypeIndex!=-1表示形参为URL,否则尝试获取参数中的URL
if (urlTypeIndex != -1) {
// Null Point check
//生成非空检查的代码
code.append(generateUrlNullCheck(urlTypeIndex));
} else {
// did not find parameter in URL type
//尝试从参数中获取URL
code.append(generateUrlAssignmentIndirectly(method));
}
//后去adaptive注解的参数
String[] value = getMethodAdaptiveValue(adaptiveAnnotation);
//判断是参数是不时Invocation
boolean hasInvocation = hasInvocationArgument(method);
//如果有Invocation参数,生非空检验
code.append(generateInvocationArgumentNullCheck(method));
//根据adaptive注解的参数和Invocation形参,确定动态扩展点的key值来源,分别有url.getMethodParameter()/url.getParameter()/url.getProtocol()
code.append(generateExtNameAssignment(value, hasInvocation));
// 生成extName非空校验
code.append(generateExtNameNullCheck(value));
//生成extendloader代码加载
code.append(generateExtensionAssignment());
// return statement
code.append(generateReturnAndInvocation(method));
}
//返回生成的代码
return code.toString();
}
此时代码已经成,返回后通过jdk的编译器,编译后返回字节码.目前已经生成的扩展类已经进入尾声.再次返回createAdaptiveExtension()方法,将返回的字节码通过injectExtension()方法为生成的代理类的参数进行注入参数如.此时动态生成扩展点结束.