概述
在这里讲解GitHub上面最新的版本的Dubbo4.3.16版本。
1.SPI机制介绍
SPI机制,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。因此,很容易的通过 SPI 机制为我们的程序提供拓展功能。对于Java的原生SPI机制在这里不多做讲解,可以在网上搜索到很多讲解的。这里通过一个例子简单说明Java的SPI机制。
2.JAVA的SPI机制
1.创建一个基础的接口类
public interface JavaSpiTestService {
String test();
}
2.对上面创建地接口类进行实现
public class JavaSpiTestServiceImpl implements JavaSpiTestService {
@Override
public String test() {
return "测试";
}
}
3.在resource目录下创建一个META-INF并在其下创建一个services目录,然后用上面创建的接口类的相对路径来创建一个文件名,文件内容是对应的实现类的相对路径
othertest.demo.impl.JavaSpiTestServiceImpl
4.进行测试
public class MainTestClass {
public static void main(String[] args) {
ServiceLoader<JavaSpiTestService> load = ServiceLoader.load(JavaSpiTestService.class);
for (JavaSpiTestService testService : load) {
System.out.println(testService.test());;
}
}
}
运行结果为
测试
3.Dubbo的SPI机制
Dubbo的SPI机制是重新实现的一套SPI机制。所有的逻辑都被封装在了ExtensionLoader类中。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下,配置内容如下。
1.目录结构
可以发现目录结构和Java的SPI机制结构相似。
2.自定义接口类
&mesp;在测试自定义的接口的时候,需要在类上加上@SPI这个注解
@SPI
public interface DubboSpiTestService {
String sayHello();
}
3.对上面的自定义实现类进行实现
public class DubboSpiTestServiceImplOne implements DubboSpiTestService {
@Override
public String sayHello() {
return "我是Dubbo的SPI机制的第一个实现类";
}
}
-----------
public class DubboSpiTestServiceImplTwo implements DubboSpiTestService {
@Override
public String sayHello() {
return "我是Dubbo的SPI机制的第二个实现";
}
}
4.配置文件的内容
Dubbo的SPI机制的配置未见的格式是键值对的形式,与Java的Spi配置文件不同
dubboOne=othertest.demo.impl.DubboSpiTestServiceImplOne
dubboTwo=othertest.demo.impl.DubboSpiTestServiceImplTwo
5.测试
public class MainTestClass {
public static void main(String[] args) {
ExtensionLoader<DubboSpiTestService> extensionLoader = ExtensionLoader.getExtensionLoader(DubboSpiTestService.class);
DubboSpiTestService dubboOne = extensionLoader.getExtension("dubboOne");
DubboSpiTestService dubboTwo = extensionLoader.getExtension("dubboTwo");
System.out.println(dubboOne.sayHello());;
System.out.println(dubboTwo.sayHello());;
}
}
最后运行结果为
我是Dubbo的SPI机制的第一个实现类
我是Dubbo的SPI机制的第二个实现
3.源码的解析
我们进入到ExtensionLoader类,在上面的main方法中我们使用到的方法是getExtension,进入到这个方法。这个方法的作用是,根据传入的扩展名找到对应的实体类。
public T getExtension(String name) {
//检查扩展名是不是空,是空会抛出异常
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
//如果指定的扩展名是“true”,返回的则是null,dubbo会缓存一个cachedDefaultName的string类型字段,这个字段保存的是贴有@SPI标签的类,默认是类名,也可以设置
if ("true".equals(name)) {
return getDefaultExtension();
}
//在已经缓存的Hodler对象的示例缓存集合中查询是否存在对应的Holder
Holder<Object> holder = getOrCreateHolder(name);
Object instance = holder.get();
//入伙对应的Holder不存在,则需要创建,并保存起来
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
//创建实例拓展类
//------------2------
instance = createExtension(name);
//-------------2--------
//保存到缓存中 holder.set(instance);
}
}
}
return (T) instance;
}
这里对上面的cachedDefaultName这个字段进行分析,找到这个字段的值进行设置的位置
private String cachedDefaultName;
private void cacheDefaultExtensionName() {
//获取type类上的SPI标签,这里的Type是调用getExtensionLoader时候传入的,在上面的main方法中可以看到我们有传入一个类的Class对象,
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if (defaultAnnotation != null) {
//获取到自定义的value
String value = defaultAnnotation.value();
//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];
}
}
}
}
对createExtension方法进行解析
private T createExtension(String name) {
//-------------1-------------
//从配置文件中读取扩展类的路径并使用类加载器加载扩展类,默认使用的是ExtensionLoader加载器,
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
//获取缓存的扩展实体类
T instance = (T) EXTENSION_INSTANCES.get(clazz);
//如果不存在就实例化然后保存起来
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
//----------------2------------
//将实例中注入,使用setter方式进行注入
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
//如果缓存的wrapperClasses集合不是空则进行注入
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
//循环创建wrapperClass
for (Class<?> wrapperClass : wrapperClasses) {
//获取wrapperClass的构造方法,并使用创建地实例作为构造参数创建wrapperClass对象,然后像Wrapper实例中注入依赖,然后赋值给instance实例
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
第一步,获取所有的扩展类;第二步,创建扩展类的实例;第三步,向扩展类中进行注入依赖;第四步,把拓展对象包裹在相应的 Wrapper 对象中
解析getExtensionClasses方法
private Map<String, Class<?>> getExtensionClasses() {
//获取缓存的class对象,所有的class对象保存在一个Map中,Map对象又封装在Dubbo自定义的一个Holder对象中
Map<String, Class<?>> classes = cachedClasses.get();
//如果缓存不存在则加锁,则加锁,然后再次检车是不是null,加锁的原因是避免为null的时候多个线程同时执行获取扩展类的操作
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
//获取扩展类,并缓存起来
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}
//--------------------loadExtensionClasses方法
private Map<String, Class<?>> loadExtensionClasses() {
//这个方法就是上面讲到的cachedDefaultName进行赋值的方法
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
//按照不同的目录来加再类
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
return extensionClasses;
}
//-----------loadDirectory方法------
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
//文件名= 文件夹路径 + type 全限定名
String fileName = dir + type;
try {
Enumeration<java.net.URL> urls;
//获取ClassLoader,默认是先获取加载ExtensionLoader类线程的上下文加载器,如果不存在才才指定ExtensionLoader类的加载器,如果加载ExtensionLoader来的加载器都不存在,则使用bootstrap类加载器
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();
//加载资源,主要就是加载配置文件然后加载文件中的类,并按照键值对的形式进行存储,其中需要注意的是有对贴有Activate标签的类的特殊处理,Activate标签的作用是在符合给定情况的时候去加载这个类 loadResource(extensionClasses, classLoader, resourceURL);
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", description file: " + fileName + ").", t);
}
}
//------------loadResource方法中的loadClass,loadClass方法是加载类的主要逻辑
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
//检查传入的type类是不是需要实例化的class的父类或者接口类,如果不是则抛错
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(clazz);
}
//如果是wrapper类型,则保存到对应的缓存中
else if (isWrapperClass(clazz)) {
cacheWrapperClass(clazz);
} else {
// 检测 clazz 是否有默认的构造方法,如果没有,则抛出异常
clazz.getConstructor();
if (StringUtils.isEmpty(name)) {
// 如果 name 为空,则尝试从 Extension 注解中获取 name,或使用小写的类名作为 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, name);
}
}
}
}
对于实例的依赖注入方法injectExtension解析
private T injectExtension(T instance) {
try {
//这里的objectFactory类就是ExtensionFactory类的实现类AdaptiveExtensionFactory
if (objectFactory != null) {
//便利实例类的方法
for (Method method : instance.getClass().getMethods()) {
//如果方法是已set开头的,且方法仅有一个参数,且方法访问级别为 public。用来确保是类的属性
if (isSetter(method)) {
/**
* Check {@link DisableInject} to see if we need auto injection for this property
*/
//如果方法上面有DisableInject这个标签就不进行注入
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
//获取 setter 方法参数类型
Class<?> pt = method.getParameterTypes()[0];
//如果字段是基础类型或这是基础类型的数组,也不进行注入
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
//获取属性名,从setter方法中获取 setName 方法对应属性名 name
String property = getSetterProperty(method);
//获取依赖对象
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
//调用setter方法进行以来的注入
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("Failed to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
objectFactory 变量的类型为 AdaptiveExtensionFactory,AdaptiveExtensionFactory 内部维护了一个 ExtensionFactory 列表,用于存储其他类型的 ExtensionFactory。Dubbo 目前提供了两种 ExtensionFactory,分别是 SpiExtensionFactory 和 SpringExtensionFactory。前者用于创建自适应的拓展,后者是用于从 Spring 的 IOC 容器中获取所需的拓展。
其中SpringExtensionFactory在初始化的时候会将Spring的ApplicationContext保存在自己的内部contexts字段中,还会注册服务关闭的钩子方法和监听器,getExtension方法获取的是注册到Spring容器中的依赖对象
public static void addApplicationContext(ApplicationContext context) {
contexts.add(context);
if (context instanceof ConfigurableApplicationContext) {
((ConfigurableApplicationContext) context).registerShutdownHook();
DubboShutdownHook.getDubboShutdownHook().unregister();
}
BeanFactoryUtils.addApplicationListener(context, shutdownHookListener);
}
public <T> T getExtension(Class<T> type, String name) {
//SPI should be get from SpiExtensionFactory
//如果是通过自定义SPI扩展的就去SpiExtensionFactory中调用getExtension方法
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
return null;
}
//获取容器中的对象
for (ApplicationContext context : contexts) {
if (context.containsBean(name)) {
Object bean = context.getBean(name);
if (type.isInstance(bean)) {
return (T) bean;
}
}
}
.....
}
在这里讲解GitHub上面最新的版本的Dubbo4.3.16版本,有部分变动。之前Dubbo版本有一个启动类DubboBootStrap类,这个类的作用是可以通过编程的方式轻松启动和停止Dubbo。其中在启动的时候会注册一个服务器关闭(这里的关闭不是强制关闭kill -9 pid 这种命令,而是kill pid这种温柔结束的方式)时候的钩子方法registerShutdownHook。这个方法会处理关闭的时候逻辑。现在这个方法在4.3.16版本中被放到了ConfigurableApplicationContext接口中,实现于AbstractApplicationContext类。在SpringExtensionFactory类中被调用。
最后
以上就是轻松长颈鹿为你收集整理的5.Dubbo源码分析----SPI机制的全部内容,希望文章能够帮你解决5.Dubbo源码分析----SPI机制所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复