文章会顺着这四个特点解析源码,在分析四个特性的源码前,会先分析一下 配置加载 。
大部分的框架的生命周期往往都必定会包括: 配置加载 和 实例使用 两个阶段。在 Dubbo SPI 中,加载的部分就是 注解 和 配置文件 。
这四个特性都有一个前置步骤,就是类和配置的加载。所以在分析特性前,需要先解析 前置的加载过程 。
加载某个接口的扩展,大概步骤大概如下:
Robot defaultExtension =
ExtensionLoader.getExtensionLoader(Robot.class).getDefaultExtension();
// ExtensionLoader.java 以加载 DefaultExtension 为入口
public T getDefaultExtension() {
getExtensionClasses(); // 加载 Robot.class 所有扩展类 从这里切入
if (StringUtils.isBlank(cachedDefaultName) || "true".equals(cachedDefaultName)) {
return null;
}
return getExtension(cachedDefaultName);
}
// 做一次线程安全的双重检测加载
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;
}
// 根据不同的策略(其实就是目录),把不同的目录下的配置 /XXXX/XXX/com.qpm.dubbo.test.spi.Robot 扫描并加载
private Map<String, Class<?>> loadExtensionClasses() {
// 通过 SPI 注解获得默认的扩展名称
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
// 通过三种不同的加载策略,加载三个目录下的所有扩展类
for (LoadingStrategy strategy : strategies) {
// loadDirecotry 加载某个目录下的Robot配置 从这里切入
loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.excludedPackages());
loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.excludedPackages());
}
return extensionClasses;
}
//
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
boolean extensionLoaderClassLoaderFirst, String... excludedPackages) {
String fileName = dir + type; // 配置文件的路径
try {
Enumeration<java.net.URL> urls = null;
// 找到 CLassLoader
ClassLoader classLoader = findClassLoader();
// try to load from ExtensionLoader's ClassLoader first
// fixme 策略可能会prefer使用 扩展类加载器,为什么?
if (extensionLoaderClassLoaderFirst) {
ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
urls = extensionLoaderClassLoader.getResources(fileName);
}
}
if(urls == null || !urls.hasMoreElements()) {
if (classLoader != null) {
// 虽然我们寻找的只是一个文件,但不同的jar包可以也打包着相同的文件
// 例如 Mysql 的 jar 带有 Java.sql.Driver SPI 的配置
// Oracle 的 jar 也会有 Java.sqlDriver SPI 的配置
// 因此需要 ClassLoader 帮忙把所有 Java.sql.Driver 的URL都定位出来
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
}
if (urls != null) {
while (urls.hasMoreElements()) { // urls 获得了多个 element
java.net.URL resourceURL = urls.nextElement(); // 获得绝对路径
// 获得了绝对路径,根据绝对路径逐个进行加载。 从这里 切入
loadResource(extensionClasses, classLoader, resourceURL, excludedPackages);
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", description file: " + fileName + ").", t);
}
}
// 加载某个资源文件的配置
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,
java.net.URL resourceURL, String... excludedPackages) {
try {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) { // 把文件读进来
String line;
while ((line = reader.readLine()) != null) {
final int ci = line.indexOf('#');
if (ci >= 0) {
line = line.substring(0, ci); // 把注释排除
}
line = line.trim();
if (line.length() > 0) {
try { // 从一行数据中提取 K-V 值
String name = null;
int i = line.indexOf('=');
if (i > 0) {
name = line.substring(0, i).trim();
line = line.substring(i + 1).trim();
}
if (line.length() > 0 && !isExcluded(line, excludedPackages)) {
// 通过 classLoader 加载类文件,然后再执行 SPI 的 loadClass 从此处切入
loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
}
} catch (Throwable t) {
IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
exceptions.put(line, e);
}
}
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", class file: " + resourceURL + ") in " + resourceURL, t);
}
}
// 真正的加载 class 对象
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
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.");
}
if (clazz.isAnnotationPresent(Adaptive.class)) {
// 假如有自适应注解,则把该 class 记录在 ExtensionLoader 自适应缓存中
cacheAdaptiveClass(clazz);
} else if (isWrapperClass(clazz)) { // 通过反射获得构造器,假如获取成功,则认为其是一个包装类
// 假如是一个包装类,则加到包装类的缓存中
cacheWrapperClass(clazz);
} else {
// 处理正在的扩展实现类
clazz.getConstructor(); // 检查无参构造函数
if (StringUtils.isEmpty(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, n); // 把类配置写入 extensionClasses, 至此 SPI 的类加载过程就结束了
}
}
}
}
复制代码
上述代码写得比较杂乱,主要是调试了 Dubbo SPI 机制,对一个标有 SPI 注解的 Robot 接口,完整的类加载跟踪,最终的结果是构造到一个 ExtensionLoader<Robot> 对象 ,里面通过扫描,加载,把 Robot 的实现类,包装类 Wrapper ,自适应 Adaptive ,自激活等信息都分别记录在 ExtensionLoader<Robot> 对象 ExtensionLoader<Robot> 对象里。
当程序执行完: ExtensionLoader.getExtensionClasses() ,就会配置文件都加载到 ExtensionLoader 中了。即为四个特性提供了足够的信息配置信息。
上一小节是分析了 SPI 配置的加载源码,这一小节会分析实例的创建。这个就很有 IOC 的味道了。获得实例的入口可以从: getExtension(String name)
// ExtensionLoader.java
public T getExtension(String name) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
return getDefaultExtension();
}
final Holder<Object> holder = getOrCreateHolder(name); // 使用 Holder 提前发布对象,类似先通过 Holder 占住坑位
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) { // double Check
instance = createExtension(name); // 从这里进入
holder.set(instance); // 把对象创建好后,设置到 句柄 中
}
}
}
return (T) instance;
}
// 构建一个 Extension
private T createExtension(String name) {
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);
}
// 依赖注入 (扩展点自动装配)
injectExtension(instance);
// 获取包装类集合 一个类是有多个包装类的
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
// Wrapper 包装类的实现过程,这里要实现的是多层包装类的包装,具体实现是这样的。
// Wrapper 包装过程和实现细节
// 假设有实现类 A,包装类 A1Wrapper,A2Wrapper,连续包装的步骤如下:
// 1、迭代到A1Wrapper,A 给 A1Wrapper 做构造参数,然后获得 A1Wrapper[持有A]
// 2、迭代到A2Wrapper,A1Wrapper 给 A2Wrapper 做构造参数,然后获得 A2Wrapper[持有A1Wrapper[A]]
// 3、有更多的迭代器时也以此类推
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
initExtension(instance);
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
复制代码
以上就是创建一个 Extension 对象的过程,简单就是:
构建 Extension 对象时,除了通过反射构造对象实例,还需要进行依赖注入(inject),要进行依赖注入,必须要先拥有对象容器(Container, 在 Spring 里就是 BeanFactory),在分析 injectExtension 方法时,要分析对象容器 Factory 的来源。
private T injectExtension(T instance) {
if (objectFactory == null) {
return instance;
}
try {
for (Method method : instance.getClass().getMethods()) {
if (!isSetter(method)) { // 获取所有的 Setter Method
continue;
}
/**
* Check {@link DisableInject} to see if we need auto injection for this property
*/
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
Class<?> pt = method.getParameterTypes()[0];
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
String property = getSetterProperty(method); // 取出参数
Object object = objectFactory.getExtension(pt, property);
// 根据参数向 Factory 获取对象 这里需要知道 objectFactory 的对象从哪里来的???
if (object != null) {
method.invoke(instance, object); // 执行 setter 对象
}
} 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;
}
// 上述并不难,关键是知道 ExtensionLoader.objectFactory 对象的来源。对象的来源也是在 ExtensionLoader 构造的时候设置的。
private ExtensionLoader(Class<?> type) {
this.type = type;
// 构造时,先获取工厂对象,用于进行依赖注入的,使用的是自适应类型
objectFactory = (type == ExtensionFactory.class ? null :
ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
复制代码
上述代码可以查看到,SPI 机制的 ExtensionFactory 容器也是通过 ExtensionLoader 获得的。通过查看 ExtensionFactory 的实现,可以发现有以下两个实现。
先查看 ExtensionFactory 的自适应实现,它就是为了
@Adaptive // 自适应注解
public class AdaptiveExtensionFactory implements ExtensionFactory {
private final List<ExtensionFactory> factories; // 有两个对象
public AdaptiveExtensionFactory() {
// 一个是 Dubbo SPI 本身的容器 (SpiExtensionFactory),
// 一个是 Spring 容器 (SpringExtensionFactory)
// 这里就意味着 SpringExtensionFactory 是被 Dubbo SPI 注册的
ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
for (String name : loader.getSupportedExtensions()) {
list.add(loader.getExtension(name));
}
factories = Collections.unmodifiableList(list);
}
... 省略代码
}
复制代码
从上面的注释上表明,SPI 的注入对象会从 SpiExtensionFactory 和 SpringExtensionFactory 这两个工厂里获得。
SpiExtensionFactory 类相比比较简单,就是获得根据接口
public class SpiExtensionFactory implements ExtensionFactory {
@Override
public <T> T getExtension(Class<T> type, String name) {
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
if (!loader.getSupportedExtensions().isEmpty()) {
return loader.getAdaptiveExtension(); // 获取自适应对象
}
}
return null;
}
}
复制代码
SpringExtensionFactory 就是链接 Dubbo SPI 容器和 Spring 容器的 桥梁 ,有了这个类, @SPI 注解就可以直接设置在 Spring 的 Bean 上,借用 Spring 更加强大的 IOC 和 AOP 功能了。
SpringExtensionFactory 的类是被设置在 dubbo-config-spring 的模块内的。
/**
* SpringExtensionFactory
*/
public class SpringExtensionFactory implements ExtensionFactory {
private static final Logger logger = LoggerFactory.getLogger(SpringExtensionFactory.class);
private static final Set<ApplicationContext> CONTEXTS = new ConcurrentHashSet<ApplicationContext>();
// Spring 一调用这个静态方法,就可以把自己的 Context 设置进来了
public static void addApplicationContext(ApplicationContext context) {
CONTEXTS.add(context);
if (context instanceof ConfigurableApplicationContext) {
((ConfigurableApplicationContext) context).registerShutdownHook();
}
}
... 省略其他方法
}
复制代码
有些拓展并不想在框架启动阶段被加载,而是希望在拓展方法被调用时,根据运行时参数进行加载。 —— Dubbo 官方文档
设定有接口:
- Robot.java @SPI
- sayHello @Adaptive
- Bumblebee implements Robot
- OptimusPrime implements Robot
复制代码
假如可以设计一个特别的 XClass implements Robot ,可以根据 参数 调用 Bumblebee 或 OptimusPrime ,岂不美哉。这就是 适应 ,假如是 Dubbo SPI 自己生成的,那就可以理解成 自适应 了。
自适应机制:从 ExtensionLoader.getAdaptiveExtension() 方法获得一个对象,这个对象是 Dubbo SPI 机制自行建立的,可以根据 URL 自定义转发。
@Test
public void testAdaptive() {
System.out.println("Dubbo SPI Adaptive ");
ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class);
// 从 getAdaptiveExtension 切入查看
Robot autoRobot = extensionLoader.getAdaptiveExtension();
assertTrue(
autoRobot.sayHelloAndReturnSelf(URL.valueOf("dubbo://127.0.0.1:20880/RobotService?robot=bumblebee"))
instanceof
Bumblebee);
// console: Hello, I am Bumblebee
assertTrue(
autoRobot.sayHelloAndReturnSelf(URL.valueOf("dubbo://127.0.0.1:20880/RobotService?robot=optimusPrime"))
instanceof
OptimusPrime);
// console:hello, I am Optimus Prime.
}
// ExtensionLoader.java 构建自适应的类
private T createAdaptiveExtension() {
try {
// getAdaptiveExtensionClass Dubbo SPI 构建类对象
// newInstance 通过反射构建
// injectExtension 依赖注入
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}
/**
* 自适应类的构建
*
* 步骤:
* 1、根据自适应的定义生成代理类代码 (XXX.java) 官方文档 https://dubbo.apache.org/zh-cn/docs/source_code_guide/adaptive-extension.html
* 2、编译成 (XXX.class) 并加载类 (Class<XXX> 对象)
*
* @return
*/
private Class<?> createAdaptiveExtensionClass() {
String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate(); // XXX.java
ClassLoader classLoader = findClassLoader();
org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
return compiler.compile(code, classLoader);
}
// 以上是构建的源码,demo构建出来的代码如下:
/**
* 这是 Dubbo SPI 自动生成的代码
* https://dubbo.apache.org/zh-cn/docs/source_code_guide/adaptive-extension.html
* 有时,有些拓展并不想在框架启动阶段被加载,而是希望在拓展方法被调用时,根据运行时参数进行加载
*/
public class Robot$Adaptive implements com.qpm.dubbo.test.spi.Robot {
public Robot sayHelloAndReturnSelf(org.apache.dubbo.common.URL arg0) {
// 1、URL 参数是否为 null, 因此 URL 参数是必须的
if (arg0 == null)
throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg0;
// 2、根据 URL 获取选择哪个扩展实例
String extName = url.getParameter("robot", "bumblebee");
if(extName == null)
throw new IllegalStateException("Failed to get extension (com.qpm.dubbo.test.spi.Robot) name from url (" + url.toString() + ") use keys([robot])");
// 3、获取实例
com.qpm.dubbo.test.spi.Robot extension
= (com.qpm.dubbo.test.spi.Robot)ExtensionLoader.getExtensionLoader(com.qpm.dubbo.test.spi.Robot.class).getExtension(extName);
// 4、执行实例方法
return extension.sayHelloAndReturnSelf(arg0);
}
}
复制代码
至此,自适应机制就实现了,其实还是比较简单的。
自激活机制 @Activate ,定义比较简单:根据 URL 的参数和组别等信息对 扩展对象 做过滤,最终获得一系列符合 URL 规则的 扩展对象 ,这个过程就是自激活过程。
源码分析如下:
@Activate(value = "robot:autobotFighter", group = "autobot") // 定义这个 扩展对象 一些用于过滤的 K-V 值
public class Bumblebee implements Robot{
@Override
public Robot sayHelloAndReturnSelf(URL url) {
System.out.println("Hello, I am Bumblebee");
return this;
}
}
@Activate(value = "robot:autobotBoss", group = "autobot")
public class OptimusPrime implements Robot{
@Override
public Robot sayHelloAndReturnSelf(URL url) {
System.out.println("hello, I am Optimus Prime.");
return this;
}
}
// ExtensionLoader.java
/**
* Get activate extensions.
*
* 自激活实现。规则如下:
* 1、REMOVE_VALUE_PREFIX+name eg: "-name" 表示过滤掉某个 Extension
* 2、URL的K-V,可以和 @Activate 中的注解 value 进行匹配
* 3、group 参数指定 @Activate 的组别,和 2 一起进行匹配,两者关系为 且
* 4、2和3条件都不合适(扩展对象没写 @Activate 注解),URL 提取出来的 value 匹配了 Extension 的 name
*
* @param url url
* @param values extension point names
* @param group group
* @return extension list which are activated
* @see org.apache.dubbo.common.extension.Activate
*/
public List<T> getActivateExtension(URL url, String[] values, String group) {
// 最终返回的激活扩展点列表
List<T> activateExtensions = new ArrayList<>();
List<String> names = values == null ? new ArrayList<>(0) : Arrays.asList(values);
// 名字中不包含 "-default"
if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
getExtensionClasses();
for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
String name = entry.getKey();
Object activate = entry.getValue();
// 获取 group、activateGroup
String[] activateGroup, activateValue;
if (activate instanceof Activate) {
// 从注解中获得 自激活 的 Group 和 Value
activateGroup = ((Activate) activate).group();
activateValue = ((Activate) activate).value();
} else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {
activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group();
activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value();
} else {
continue;
}
if (isMatchGroup(group, activateGroup) // 匹配组是否一致
&& !names.contains(name)
&& !names.contains(REMOVE_VALUE_PREFIX + name)
&& isActive(activateValue, url)) {
activateExtensions.add(getExtension(name));
}
}
// 设置排序
activateExtensions.sort(ActivateComparator.COMPARATOR);
}
// 自激活不一定要走 K-V 匹配,也可以直接通过 Extensions.name 直接进行匹配,这时候就不一定需要 @Activate 注解了
List<T> loadedExtensions = new ArrayList<>();
for (int i = 0; i < names.size(); i++) {
String name = names.get(i);
if (!name.startsWith(REMOVE_VALUE_PREFIX)
&& !names.contains(REMOVE_VALUE_PREFIX + name)) {
if (DEFAULT_KEY.equals(name)) {
if (!loadedExtensions.isEmpty()) {
// 假如是 default,则强行插入在 扩展链条 的第一位
activateExtensions.addAll(0, loadedExtensions);
loadedExtensions.clear();
}
} else {
loadedExtensions.add(getExtension(name));
}
}
}
if (!loadedExtensions.isEmpty()) {
activateExtensions.addAll(loadedExtensions);
}
return activateExtensions;
}
// 判断一个 @Activate 注解的k-v值,是否和 url 的值相匹配
private boolean isActive(String[] keys, URL url) {
if (keys.length == 0) {
return true;
}
for (String key : keys) {
// @Active(value="key1:value1, key2:value2")
String keyValue = null;
if (key.contains(":")) {
String[] arr = key.split(":");
key = arr[0]; // 注解K
keyValue = arr[1]; // 注解 V
}
for (Map.Entry<String, String> entry : url.getParameters().entrySet()) {
String k = entry.getKey(); // URL K
String v = entry.getValue(); // URL V
// 查看是否匹配。假如 URL 和 注解 有一项匹配就可以返回 true
if ((k.equals(key) || k.endsWith("." + key))
&& ((keyValue != null && keyValue.equals(v)) || (keyValue == null && ConfigUtils.isNotEmpty(v)))) {
return true;
}
}
}
return false;
}
复制代码
自激活至此也分析完了,整体也说不是很难,但直接看代码去反推匹配规则会比较吃力。整体来说就是根据条件返回符合条件的 扩展对象 。