dubbo源码之SPI机制源码

在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机制去解析的

猜你喜欢

转载自blog.csdn.net/CPLASF_/article/details/114675095