springboot 中的扩展加载(SpringFactoriesLoader)
在 SpringBoot 启动类上都会标注@SpringBootApplication
这个注解,其中的最重要的组成是@EnableAutoConfiguration
, 再进入会发现是 @Import (AutoConfigurationImportSelector.class)
这个注解在起作用。
自动加载配置的逻辑是:selectImports->selectImports->getAutoConfigurationEntry
,不过这里不是分析 SpringBoot 启动流程
,我们关心的是他如何加载配置的。可以在getAutoConfigurationEntry
看到使用的是SpringFactoriesLoader
,主要代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| public static List < String > loadFactoryNames(Class < ? > factoryClass, @Nullable ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); }
private static Map < String, List < String >> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap < String, String > result = cache.get(classLoader); if (result != null) { return result; }
try { Enumeration < URL > urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); result = new LinkedMultiValueMap < > (); while (urls.hasMoreElements()) { URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry < ? , ? > entry : properties.entrySet()) { String factoryClassName = ((String) entry.getKey()).trim(); for (String factoryName: StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { result.add(factoryClassName, factoryName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } }
|
这样就把我们配置在 META-INF/spring.factories
中的配置类给找出来了,完成了自动配置的加载(spring 的 @import 机制会完成这个 bean 的注入过程)
Java SPI
SPI 全称 Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件,可以根据使用者的配置,来加载接口的具体实现类。
使用
先看看怎么使用。首先创建一个Repository
接口,和两个实现类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public interface Repository { void connect(); } public class MongoRepository implements Repository { @Override public void connect() { System.out.println("mongo repository"); } } public class MysqlRepository implements Repository { @Override public void connect() { System.out.println("mysql connected"); } }
|
然后在META-INF/services
下新建一个与接口同名的文件:com.java.Repository
,里面的内容是具体实现类的类的全名:
1 2
| com.java.impl.MongoRepository com.java.MysqlRepository
|
运行方法:
1 2 3 4 5 6 7 8
| @Test public void jdkSpi() { ServiceLoader<Repository> serviceLoader = ServiceLoader.load(Repository.class); Iterator<Repository> iterable = serviceLoader.iterator(); while (iterable.hasNext()) { System.out.println(iterable.next().getClass().getName()); } }
|
可以在控制台上看到,输出了配置文件中的类,也就是代表了配置文件中的类已经被加载了。
原理
jdk 的这种机制,把约定(接口)和实现分离,我们当引入具体实现的时候,不会给服务使用方带来任何代码上的修改,并且只有服务方主动使用的时候才会真正的去初始化,完成动态的加载。
可以看到 jdk 是通过 ServiceLoader
来实现, 它的 load()
流程如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public static <S> ServiceLoader<S> load(Class<S> service) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); }
public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader){ return new ServiceLoader<>(service, loader); }
private ServiceLoader(Class<S> svc, ClassLoader cl) { service = Objects.requireNonNull(svc, "Service interface cannot be null"); loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; reload(); }
|
上面的内容是获取当前类的加载器,传给成员变量loader
,并进入reload
流程, reload
是初始化了一个LazyIterator
对象, 看看它的next()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| public S next() { if (acc == null) { return nextService(); } else { PrivilegedAction<S> action = new PrivilegedAction<S>() { public S run() { return nextService(); } }; return AccessController.doPrivileged(action, acc); } }
private S nextService() { if (!hasNextService()) throw new NoSuchElementException(); String cn = nextName; nextName = null; Class<?> c = null; try { c = Class.forName(cn, false, loader); } catch (ClassNotFoundException x) { fail(service, "Provider " + cn + " not found"); } if (!service.isAssignableFrom(c)) { fail(service, "Provider " + cn + " not a subtype"); } try { S p = service.cast(c.newInstance()); providers.put(cn, p); return p; } catch (Throwable x) { fail(service, "Provider " + cn + " could not be instantiated", x); } throw new Error(); }
private boolean hasNextService() { if (nextName != null) { return true; } if (configs == null) { try { String fullName = PREFIX + service.getName(); if (loader == null) configs = ClassLoader.getSystemResources(fullName); else configs = loader.getResources(fullName); } catch (IOException x) { fail(service, "Error locating configuration files", x); } } while ((pending == null) || !pending.hasNext()) { if (!configs.hasMoreElements()) { return false; } pending = parse(service, configs.nextElement()); } nextName = pending.next(); return true; }
|
可以看到,整个流程还是很清晰的,就是去找META-INF/service
下对应的class全名的文件,然后返回出去。
Dubbo 中 扩展点的加载
Dubbo 文档中描述 Dubbo SPI 改进了 JDK SPI 的以下问题:
- JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
- 如果扩展点加载失败,连扩展点的名称都拿不到了(不会上报真正失败的原因)。
- 增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。
dubbo的扩展点加载具体以下特点:
- 自动包装:
ExtensionLoader
在加载扩展点时,如果加载到的扩展点有拷贝构造函数,则判定为扩展点 Wrapper
类。Wrapper
类可以为扩展点之上做一些处理,类似于 AOP - 自动装配: 扩展点实现类的成员如果是其他扩展点,
ExtensionLoader
会自动注入依赖的扩展点。 - 扩展点自适应:
ExtensionLoader
注入的依赖扩展点是一个 Adaptive
实例,直到扩展点方法执行时才决定调用是哪一个扩展点实现。 - 扩展点自动激活,如果扩展点有多个实现类,可以使用
@Activate
来自动激活,简化配置
使用
借用官网的例子,新建接口Robot
和两个实现类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @SPI public interface Robot { void sayHello(); }
public class Bumblebee implements Robot { @Override public void sayHello() { System.out.println("Hello, I am Bumblebee."); } }
public class Bumblebee implements Robot { @Override public void sayHello() { System.out.println("Hello, I am Bumblebee."); } }
|
然后在 META-INF/dubbo/
下新建与接口同名的文件com.code.service.Robot
,并配置扩展实现类:
1 2
| bumblebee = com.code.service.impl.Bumblebee optimusPrime = com.code.service.impl.OptimusPrime
|
执行:
1 2 3 4 5
| public static void main(String[] args) { ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class); Robot optimusPrime = extensionLoader.getExtension("optimusPrime"); optimusPrime.sayHello(); }
|
控制台输出:
1
| Hello, I am Optimus Prime.
|
加载扩展对象
上面的使用例子可以看出,首先通过 ExtensionLoader 的 getExtensionLoader 方法获取一个 ExtensionLoader 实例,然后再通过 ExtensionLoader 的 getExtension 方法获取拓展类对象。
getExtensionLoader
getExtensionLoader()
用于从缓存中获取与拓展类对应的 ExtensionLoader,若缓存未命中,则创建一个新的实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { if (type == null) throw new IllegalArgumentException("Extension type == null"); if (!type.isInterface()) { throw new IllegalArgumentException("Extension type(" + type + ") is not interface!"); } if (!withExtensionAnnotation(type)) { throw new IllegalArgumentException("Extension type(" + type + ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!"); } ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); if (loader == null) { EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type)); loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); } return loader; }
|
getExtension
getExtension(String name)
获取扩展类对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public T getExtension(String name) { if (name == null || name.length() == 0) throw new IllegalArgumentException("Extension name == null"); if ("true".equals(name)) { // 获取默认的拓展实现类 return getDefaultExtension(); } // Holder 用于持有目标对象 Holder<Object> holder = cachedInstances.get(name); if (holder == null) { cachedInstances.putIfAbsent(name, new Holder<Object>()); holder = cachedInstances.get(name); } Object instance = holder.get(); // 双重检查 if (instance == null) { synchronized (holder) { instance = holder.get(); if (instance == null) { // 创建拓展实例 instance = createExtension(name); // 设置实例到 holder 中 holder.set(instance); } } } return (T) instance; }
|
上述逻辑较为简单,尝试从缓存中获取对象,若缓存没有则创建扩展对象,创建对象createExtension(String name)
流程如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| 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; if (wrapperClasses != null && !wrapperClasses.isEmpty()) { // 循环创建 Wrapper 实例 for (Class<?> wrapperClass : wrapperClasses) { // 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例。 // 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量 // 所以最终返回的是一个 wrapper 类的实例 instance = injectExtension( (T) wrapperClass.getConstructor(type).newInstance(instance)); } } return instance; } catch (Throwable t) { throw new IllegalStateException("..."); } }
|
这里主要分为四个步骤
- 通过 getExtensionClasses 获取所有的拓展类
- 通过反射创建拓展对象
- 向拓展对象中注入依赖(dubbo 的 IOC 机制)
- 将拓展对象包裹在相应的
Wrapper
对象中
获取所有扩展类
getExtensionClasses()
会根据配置文件解析出 扩展项->扩展类
的映射关系表(Map<名称, 拓展类>),流程如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| 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; }
private Map<String, Class<?>> loadExtensionClasses() { final SPI defaultAnnotation = type.getAnnotation(SPI.class); 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..."); } if (names.length == 1) { cachedDefaultName = names[0]; } } } Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>(); loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY); loadDirectory(extensionClasses, DUBBO_DIRECTORY); loadDirectory(extensionClasses, SERVICES_DIRECTORY); return extensionClasses; }
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir) { String fileName = dir + type.getName(); try { Enumeration<java.net.URL> urls; 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(); loadResource(extensionClasses, classLoader, resourceURL); } } } catch (Throwable t) { logger.error("..."); } }
|
获取缓存的逻辑不复杂,就是有点长, 到这里基本就完成了上述的1、2步,获取了扩展实现类并通过反射创建了扩展对象
Dubbo IOC
Dubbo IOC 是通过 setter 方法注入依赖。Dubbo 首先会通过反射获取到实例的所有方法,然后再遍历方法列表,检测方法名是否具有 setter 方法特征。若有,则通过 ObjectFactory 获取依赖对象,最后通过反射调用 setter 方法将依赖设置到目标对象中。整个过程对应的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| private T injectExtension(T instance) { try { if (objectFactory != null) { // 遍历目标类的所有方法 for (Method method : instance.getClass().getMethods()) { // 检测方法是否以 set 开头,且方法仅有一个参数,且方法访问级别为 public if (method.getName().startsWith("set") && method.getParameterTypes().length == 1 && Modifier.isPublic(method.getModifiers())) { // 获取 setter 方法参数类型 Class<?> pt = method.getParameterTypes()[0]; try { // 获取属性名,比如 setName 方法对应属性名 name String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : ""; // 从 ObjectFactory 中获取依赖对象 Object object = objectFactory.getExtension(pt, property); if (object != null) { // 通过反射调用 setter 方法设置依赖 method.invoke(instance, object); } } catch (Exception e) { logger.error("fail to inject via method..."); } } } } } catch (Exception e) { logger.error(e.getMessage(), e); } return instance; }
|
自适应扩展
Dubbo 的扩展都是通过 SPI 机制进行加载,有时候希望扩展被调用前,根据运行参数进行加载。这里感觉起来就很矛盾,既然没有被加载,怎么去调用呢? Dubbo 的自适应扩展机制解决了这个问题。
这里的代码有些复杂,主要的流程是通过@Adaptive
注解标注在类或方法上,如果是标注在类上就会生成代理类(这种模式比较简单),如果标注在方法上,就会由框架自动生成加载扩展的逻辑。