我们说dubbo的spi机制和Java提供的spi机制,有一个很大的区别,dubbo的spi机制中,需要在文件中,指定key,我们在使用的时候可以根据key,只加载我们需要使用的实现类,那除了直接在文件中,通过
key=com.xxx.interfaceImpl这种方式之外,还可以通过@Extension这个注解来指定key,dubbo源码在解析的时候,会判断,如果没有在文件中指定key,会解析其实现类上是否有这个注解
文章中,指定路径指:META-INF/services
指定文件是指这个路径下的文件
com.alibaba.dubbo.common.extension.ExtensionLoader#loadClass
在spi机制被使用的时候,是会去指定路径下,解析指定的文件,然后从文件中解析出对应key和value,解析之后,会有一系列逻辑的判断,上面的这个方法,就是解析之后,对实现类进行的一系列判断,具体是如何执行到这一个方法的,在前面 dubbo源码之SPI机制源码中有介绍
其实大致就是说:如果在调用spi的时候,会一次去指定路径下解析指定的文件,解析到文件之后,会一行一行的解析,以 "="为分隔符,前面的是key,后面的是value,解析到之后,会根据value(全类名),生成对应的class文件,然后进入到这个方法中,对class进行解析
/**
* 这个方法主要是将name和clazz赋值到extensionClasses中,只是在put之前,会有一系列的逻辑判断,会区分出来是哪种类型的类
* @param extensionClasses
* @param resourceURL
* @param clazz
* @param name
* @throws NoSuchMethodException
*/
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
/**
* 1.判断clazz是否是type的实现类
* 如果type是Protocol,那么这里的clazz就是文件中配置的HttpProtocol实现类
* 如果不是type的实现类,就抛出异常
*/
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error when load extension class(interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + "is not subtype of interface.");
}
/**
* 2.判断当前实现类上是否有@Adaptive注解,需要注意的是:一个接口,只允许有一个adaptive实现类
* 如果有多个,就抛出异常
* cachedAdaptiveClass中保存的就是type对应的adaptive实现类,这里获取到的是我们自己定义的adaptive实现类
*/
if (clazz.isAnnotationPresent(Adaptive.class)) {
if (cachedAdaptiveClass == null) {
cachedAdaptiveClass = clazz;
} else if (!cachedAdaptiveClass.equals(clazz)) {
throw new IllegalStateException("More than 1 adaptive class found: "
+ cachedAdaptiveClass.getClass().getName()
+ ", " + clazz.getClass().getName());
}
} else if (isWrapperClass(clazz)) {
/**
* 在判断是否是包装类的时候,就看对应类中是否有目标接口的构造函数
* 3.wrapper类的处理,如果当前clazz是type实现类的包装类,就暂时将包装类存入到一个集合中
* CarWrapper就会进入到这里来处理
*/
Set<Class<?>> wrappers = cachedWrapperClasses;
if (wrappers == null) {
cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
wrappers = cachedWrapperClasses;
}
wrappers.add(clazz);
} else {
/**
* 4.1 进入到这里,表示既不是包装类,也没有添加@Adaptive注解
* 必须要有无参构造函数,因为是通过class.newInstance()来初始化的
*/
clazz.getConstructor();
/**
* 4.2 如果在META-INF下的文件中,没有配置name,就从实现类上去判断,是否有添加@Extension注解,如果有添加,就返回
* @Extension 注解对应的value
*/
if (name == null || name.length() == 0) {
name = findAnnotationName(clazz);
if (name.length() == 0) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
}
/**
* 4.3 对name进行拆分
* 并且判断是否有添加@Activate注解
*/
String[] names = NAME_SEPARATOR.split(name);
if (names != null && names.length > 0) {
Activate activate = clazz.getAnnotation(Activate.class);
if (activate != null) {
cachedActivates.put(names[0], activate);
}
for (String n : names) {
if (!cachedNames.containsKey(clazz)) {
cachedNames.put(clazz, n);
}
Class<?> c = extensionClasses.get(n);
if (c == null) {
extensionClasses.put(n, clazz);
} else if (c != clazz) {
throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
}
}
}
}
}
我们要关心的是4.2这个注释
我们可以看到,有一个判断:
这里的判断也很简单,如果name为null,就表示我在文件中是这样来配置的:
com.xxxx.xxxx.InterfaceImpl
正常情况下,配置的时候,是需要指定key的,那如果是这种情况,dubbo会取实现类上解析是否有@Extension注解
也就是下面这个方法中
private String findAnnotationName(Class<?> clazz) {
com.alibaba.dubbo.common.Extension extension = clazz.getAnnotation(com.alibaba.dubbo.common.Extension.class);
/**
* 1.如果没有添加注解,就取class的simpleName,然后判断是否是以接口名结尾的,如果是,就截取其前面部分作为name
* RedCarInterface,如果实现类名是这个,就会截取red作为name
*/
if (extension == null) {
String name = clazz.getSimpleName();
if (name.endsWith(type.getSimpleName())) {
name = name.substring(0, name.length() - type.getSimpleName().length());
}
return name.toLowerCase();
}
/**
* 2.如果配置了注解,就返回其value值,value值就是实现类对应的name
*/
return extension.value();
}
这里可以看到,如果@Extension注解也没有加,dubbo还会进行一次挣扎,也就是直接解析实现类,取其simpleName,然后判断实现类是否是以接口结尾的,其实举个例子就是这样的
假如我自定义了一个protocol的实现类:MySelfProtocol,有三种情况
1.如果我在文件中指定:mySelf=com.xxx.MySelfProtocol,那就会使用文件中的name作为key(name)
2.如果我既没有在文件中指定name,但是添加了@Extension(value = “mySelf”),那就会使用这里的value作为name来赋值
3.如果我既没有在文件中指定,也没有在实现类上添加注解,那就会解析MySelfProtocol这个字符串,然后截取mySelf作为name,并将mySelf转换为小写
本文探讨了Dubbo的SPI扩展机制,特别是@Extension注解的作用。当不在配置文件中指定key时,Dubbo会检查实现类上的@Extension注解来确定key。在指定路径"META-INF/services"的文件中,Dubbo按行解析以'='分隔的key-value对。如果name为null,Dubbo会尝试从实现类的简单名称中获取key。三种可能的情况包括:1) 文件中指定name;2) 使用@Extension注解指定name;3) 实现类名称作为默认name。
2146

被折叠的 条评论
为什么被折叠?



