在dubbo中,对Java中的spi机制进行了扩展,具体spi机制的使用,就不说了,在dubbo中进行了如下的扩展
1.可以通过xxxname = com.xxxx.MyFilter的配置方式,来指定自定义实现类的name
2.在使用的时候,可以根据name获取指定的扩展类,而不是像Java的spi机制直接获取所有的扩展类
在源码层面,SPI机制最为重要的类就是ExtensionLoader类,在该类中,有三个比较重要的方法
getExtension(String name)
getActivateExtension(URL url, String key)
getAdaptiveExtension()
其中,最为基础的方法是getExtension()方法,因为在该方法中,会去解析所有的扩展配置文件中配置的扩展类,然后解析在文件中配置的扩展类,是普通的扩展类,还是
@Active注解修饰的类、还是@Adaptive修饰的类、还是包装类
getExtension(String name)
在getExtension(String name)方法中,会通过DCL的方式去判断是否已经解析,如果没有解析,就会调用到
com.alibaba.dubbo.common.extension.ExtensionLoader#createExtension
去开始解析当前type所有的扩展类
/**
* 根据name创建对应的实现类,以protocol为例,如果入参http,则这里创建的是httpProtocol
*
* 1、创建class对象
* 2、属性注入
* 3、AOP
*/
private T createExtension(String name) {
/**
* 下面这一行代码中的getExtensionClasses()是最为重要的代码之一
*/
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
/**
* 1.前面都是缓存的判断,这里会根据class创建一个对象
*/
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
/**
* 2.IOC进行注入依赖
*/
injectExtension(instance);
/**
* 3.aop 进行wrapper类的包装
* cachedWrapperClasses这个变量是在解析META-INF.services目录下文件的时候,赋值的
*/
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
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对应的扩展类的时候,会进行以下几个操作
1.解析完所有的扩展类之后,会先进行属性注入,这里的属性注入和spring中的概念还不太一样,这里的注入只支持通过set方法进行注入
2.通过类似于aop的操作,对包装类进行处理,将包装类包裹在目标方法外层
包装类的处理这里也依赖于getExtensionClasses()这个方法,这个方法是去解析当前所配置的所有扩展类,举个例子:比如protocol接口,在这里就会去解析dubbo自己提供的protocol接口的实现类,和我们提供的protocol实现类
解析配置文件
getExtensionClasses()
解析配置文件的原理,要从getExtensionClasses()这个方法看起:
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();
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;
}
这里的cachedClasses中存储的是一个map集合,这个map集合就是解析出来扩展类对应的name和对应的实现类
loadExtensionClasses()
/**
* 这个方法返回的map是type对应的扩展类
* 比如:type是protocol的时候,会返回RegistryProtocol、HttpProtocol等
* @return
*/
private Map<String, Class<?>> loadExtensionClasses() {
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
/**
* 1.解析@SPI注解上对应的默认的实现类,只允许有一个默认的实现类,如果@SPI("dubbo,http")这样就会跑出异常
* 将默认的实现,赋值到全局变量:cachedDefaultName中
*/
if (defaultAnnotation != null) {
String value = defaultAnnotation.value();
if ((value = value.trim()).length() > 0) {
String[] names = NAME_SEPARATOR.split(value);
if (names.length > 1) {
throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
+ ": " + Arrays.toString(names));
}
if (names.length == 1) cachedDefaultName = names[0];
}
}
/**
* 2.从指定的目录下的文件中解析
* 在解析到之后,会赋值到extensionClasses中,在2.6之后的版本中,会兼容com.alibaba
*/
Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
loadDirectory(extensionClasses, DUBBO_DIRECTORY);
loadDirectory(extensionClasses, SERVICES_DIRECTORY);
return extensionClasses;
}
我们一层一层往下分析:这里的loadExtensionClasses()方法中会判断当前目标接口是否添加了@SPI注解
然后会将@SPI注解中设置的name,设置为默认的name
最为核心的是三个loadDirectory()方法
我们通常说,在自定义扩展类的时候,需要在指定的路径下,分别有三个,是哪三个呢?就是这里的
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/";
我们接着来看loadDirectory方法
loadDirectory()
/**
* 从指定的dir目录下,查找class的实现类,通过class.forName()初始化,然后放入到extensionClasses中;
* @param extensionClasses
* @param dir
*/
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir) {
String fileName = dir + type.getName();
try {
Enumeration<java.net.URL> urls;
/**
* 1.获取对应的classLoader
* 然后获取到对应文件中的所有url
*/
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();
/**
* 2.加载对应的实现类,这里的resourceURL就是文件中的:http=com.alibaba.....HttpProtocol
*/
loadResource(extensionClasses, classLoader, resourceURL);
}
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: " +
type + ", description file: " + fileName + ").", t);
}
}
这个方法主要是根据路径地址 + typeName,拼接全类名,
loadClass
中间还有一个loadResource方法,就不贴代码了,loadResource()方法主要是根据
/**
* 这个方法主要是将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实现类
*/
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实现类
* 如果有多个,就抛出异常
*/
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.进入到这里,表示既不是包装类,也没有添加@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进行拆分
* 并且判断
*/
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());
}
}
}
}
}
loadClass主要是将解析到的clazz进行处理,在注释中已经写的比较明白了
1.首先判断是否是 对应的实现类
2.然后判断是否有添加@Adaptive注解,如果是第一次解析到adaptive实现类,就会放入到cachedAdaptiveClass中,如果一个接口有两个adaptive实现类,就会报错,在dubbo中是不允许的
3.然后判断是否是包装类
4.最后,除了上面的几种情况,就是一个普通的实现类了,但是也会判断是否有添加@Activate注解,如果有添加,就放入到cachedActivates中,这是一个默认激活的配置
在这个方法中,需要关注几个集合
cachedAdaptiveClass:存储的是程序员实现的adaptive实现类
cachedWrapperClasses:存储的是当前接口protocol的包装类
cachedActivates:存放的是加了@Activate注解的类
cachedNames:存储的是普通的实现类
getAdaptiveExtension()
如果我们没有声明接口的adaptive实现类,那在调用这个方法的时候,dubbo会默认帮我们生成一个adaptive实现类
/**
* 获取添加了@Adaptive注解的实现类
*/
public T getAdaptiveExtension() {
Object instance = cachedAdaptiveInstance.get();
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("fail to create adaptive instance: " + t.toString(), t);
}
}
}
} else {
throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
}
}
return (T) instance;
}
private T createAdaptiveExtension() {
try {
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}
private Class<?> getAdaptiveExtensionClass() {
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
这里可以看到,在调用方法的时候,会先从cachedAdaptiveClass 中取,如果没有,就调用createAdaptiveExtensionClass()方法
private Class<?> createAdaptiveExtensionClass() {
String code = createAdaptiveExtensionClassCode();
ClassLoader classLoader = findClassLoader();
com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
return compiler.compile(code, classLoader);
}
这里createAdaptiveExtensionClassCode()方法,没太看明白,但是根据debug的结果来看,是拼接了一个类的字符串,然后通过 compiler.compile(code, classLoader);去生成一个adaptive实现类
下面是在启动的时候,dubbo生成的protocol的adaptive实现类,可以看到,export 和refer对应的实现方法中都有逻辑,但是destroy和getDefaultPort默认的实现方法中是抛出异常了,
这个adaptive的机制有关系:如果让dubbo自己动态的去生成adaptive实现类的时候,只会对目标接口中加了@Adaptive注解的方法进行实现
package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
public void destroy() {
throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
public int getDefaultPort() {
throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException {
if (arg1 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg1;
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if (extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
}
public com.alibaba.dubbo.rpc.Exporter
export (com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
com.alibaba.dubbo.common.URL url = arg0.getUrl();
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if (extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.export(arg0);
}
}
@SPI("dubbo")
public interface Protocol {
int getDefaultPort();
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
@Adaptive
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
void destroy();
}
getActivateExtension()
/**
* Get activate extensions.
* 获取指定type对应的active实现类
* 1.遍历所有的active实现类,cachedActivates 这个集合在调用getExtension的时候,会将实现类中加了@Active注解的类放到该集合中
* 2.然后会判断group是否符合要求
* 3.会判断请求该方法的时候,如果指定了names,那就过滤出来指定的active实现类
* 4.接着判断注解中指定的value,在url的parameters中是否存在
* @param url url
* @param values extension point names
* @param group group
* @return extension list which are activated
* @see com.alibaba.dubbo.common.extension.Activate
*/
public List<T> getActivateExtension(URL url, String[] values, String group) {
List<T> exts = new ArrayList<T>();
List<String> names = values == null ? new ArrayList<String>(0) : Arrays.asList(values);
if (!names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {
getExtensionClasses();
for (Map.Entry<String, Activate> entry : cachedActivates.entrySet()) {
String name = entry.getKey();
Activate activate = entry.getValue();
/**
* 1.判断activate对应的group是否符合当前入参的group
*/
if (isMatchGroup(group, activate.group())) {
/**
* 2.根据name获取对应的实现类;这里的name,应该就是文件中filter对应的key
* 3.在isActive()方法中,判断当前url是否包含filter指定的value
*/
T ext = getExtension(name);
if (!names.contains(name)
&& !names.contains(Constants.REMOVE_VALUE_PREFIX + name)
&& isActive(activate, url)) {
exts.add(ext);
}
}
}
Collections.sort(exts, ActivateComparator.COMPARATOR);
}
List<T> usrs = new ArrayList<T>();
for (int i = 0; i < names.size(); i++) {
String name = names.get(i);
if (!name.startsWith(Constants.REMOVE_VALUE_PREFIX)
&& !names.contains(Constants.REMOVE_VALUE_PREFIX + name)) {
if (Constants.DEFAULT_KEY.equals(name)) {
if (!usrs.isEmpty()) {
exts.addAll(0, usrs);
usrs.clear();
}
} else {
T ext = getExtension(name);
usrs.add(ext);
}
}
}
if (!usrs.isEmpty()) {
exts.addAll(usrs);
}
return exts;
}
这里面比较核心的是isActive()方法,
/**
* 判断active注解中的value
* 如果我们在实现类上的active注解中,配置了value参数,表示在buildFilter的时候,不仅要满足group,还要满足:
* 在请求的url中,对应的parameter有value的值,且其值不为null
* @param activate:name对应的实现类上,指定的value
* @param url
* @return
*/
private boolean isActive(Activate activate, URL url) {
String[] keys = activate.value();
if (keys.length == 0) {
return true;
}
for (String key : keys) {
for (Map.Entry<String, String> entry : url.getParameters().entrySet()) {
String k = entry.getKey();
String v = entry.getValue();
if ((k.equals(key) || k.endsWith("." + key))
&& ConfigUtils.isNotEmpty(v)) {
return true;
}
}
}
return false;
}
总结
所以,对于dubbo的SPI机制,最为核心的是对所有扩展方法的解析,active和adaptive都依赖于对配置文件中,自定义实现类的解析,最后会对其进行wrapper包装类的处理,最为典型的应用:就是filter的使用,在服务暴露和服务引入的过程中,会经过一系列包装类和filter的处理,其依赖的就是SPI机制去解析的