JAVA SPI
java中提供了一种服务发现的机制,叫做spi(Service Provider Interface),其约定大致是:
1、当服务提供者提供了接口的一种具体实现后,在jar包的META-INF/services目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名;
2、接口实现类所在的jar包放在主程序的classpath中;
3、主程序通过java.util.ServiceLoder动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM;
4、SPI的实现类必须携带一个不带参数的构造方法;
这一块不熟悉的可以搜一下 ServiceLoder 相关的使用,网上有很多例子(jdbc,sl4j等都运用了该机制实现了其特性)。
Dubbo @SPI
在dubbo框架中有一个 @SPI (dubbo提供的一种服务发现机制)标签,这个标签是一个神奇的存在。dubbo提供的自有spi功能并不是基于原生java提供的spi相关的api实现的,而是自起一套;个人觉得比原生的spi更灵活和丰富。
通常用法是:被它标记的接口可以通过一个dubbo提供的ExtensionLoader的相关api动态生成一个自适应类(我们可以暂称之为代理类),这个类主要功能就是根据 URL url 参数里特有的属性去执行真正目标类的对应方法;
当然也可以通过名称直接找到配置文件里的真实类,然后直接使用该真实类。
ExtensionLoader
dubbo的spi特性主要是 ExtensionLoader 为入口,提供了以下api
这些方法中只有
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type)
这个方法是静态方法且返回值是ExtensionLoader对象,参数是一个Class对象,表示我们要获得哪一种类型的Loader;所以我们要使用它的api,就需要先使用该方法获得对应的实例(这是使用ExtensionLoader的一个技巧);
这个方法主要功能是返回一个 ExtensionLoader 对象,供后续调用。下面大概讲一下其他api的功能
public T getExtension(String name)
这个方法功能是通过配置文件配置的 K(name) --- V(T) 配置,实例化key对应的类并返回,加载配置文件的方法是这个方法
// synchronized in getExtensionClasses
private Map<String, Class<?>> loadExtensionClasses() {
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if (defaultAnnotation != null) {
String value = defaultAnnotation.value();
if (value != null && (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];
}
}
Object me = this;
Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
//META-INF/dubbo/internal/
loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
//META-INF/dubbo/
loadFile(extensionClasses, DUBBO_DIRECTORY);
//META-INF/services/
loadFile(extensionClasses, SERVICES_DIRECTORY);
return extensionClasses;
}
加载的顺序分别是:
META-INF/dubbo/internal/
META-INF/dubbo/
META-INF/services/
spi配置文件
我们看看这些配置文件长什么样子(我们以com.alibaba.dubbo.remoting.Transporter这个SPI接口为例子):
1、文件名字是接口名
2、内容格式 name=类全限定名
这样我们就可以通过以下api获得 NettyTransporter 实例了:
public static void main(String[] args) throws Exception {
Transporter t = ExtensionLoader.getExtensionLoader(Transporter.class).getExtension("netty");
System.out.println(t);
}
打印结果
Compiler
上面我们讲了通过名字找到对应的实现类,但是实际框架中通过代理类去执行目标类的使用方式也是非常多的,这个主要通过生成一个叫自适应(Adaptive)类来完成;
在生成消费端配置bean的类
com.alibaba.dubbo.config.ReferenceConfig
就用了
private static final Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
private static final Cluster cluster = ExtensionLoader.getExtensionLoader(Cluster.class).getAdaptiveExtension();
private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
生成自适应类,主要实现是通过动态拼接成一个完整类的代码,然后调用编译模块对代码进行编译生成对应的实例;不得不说这个操作很别处心裁。
我们来跟一下
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 Class<?> createAdaptiveExtensionClass() 方法
private Class<?> createAdaptiveExtensionClass() {
//动态生成代码
String code = createAdaptiveExtensionClassCode();
System.out.println("code>>>" + code);
//获得classloader
ClassLoader classLoader = findClassLoader();
//获得编译器(这里也是通过SPI机制获得的)
com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
//自适应类编译完成,返回Class对象
return compiler.compile(code, classLoader);
}
自适应类源码动态生成
private String createAdaptiveExtensionClassCode()
能生成动态类的接口必须是被@SPI标记,然后生成的方法个数是根据接口里被 @Adaptive 标记的那些方法;
我们看一下 com.alibaba.dubbo.remoting.Transporter 生成的自适应类的代码(通过debug可以获得)
package com.alibaba.dubbo.demo.provider;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Transporter$Adaptive implements com.alibaba.dubbo.remoting.Transporter {
public com.alibaba.dubbo.remoting.Client connect(com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.remoting.RemotingException {
if (arg0 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg0;
String extName = url.getParameter("client", url.getParameter("transporter", "netty"));
if (extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([client, transporter])");
com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
return extension.connect(arg0, arg1);
}
public com.alibaba.dubbo.remoting.Server bind(com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.remoting.RemotingException {
if (arg0 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg0;
String extName = url.getParameter("server", url.getParameter("transporter", "netty"));
if (extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([server, transporter])");
com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
return extension.bind(arg0, arg1);
}
}
可以看出自适应类实现了接口,关键地方就是每个方法最后都是通过
public T getExtension(String name)
这个方法找到真实目标类,来做处理。
编译
调用 com.alibaba.dubbo.common.compiler.Compiler 实例编译生成Class对象。
Compiler 接口最底层实现主要是两个
JdkCompiler 和 JavassistCompiler,而 AbstractCompiler是封装了一些公共方法,AdaptiveCompiler是一个自适应类。
有兴趣的可以看看Javasist和jdkcompiler的实现,这种思路很特别,也许以后我们开发过程中也能使用到;
有一点需要注意,dubbo生成的自适应类有一点就是方法执行的时候需要依赖URL类,在生成自适应类源码的时候会循环遍历的去找该类型,这个是强制依赖的;