JAVA的SPI机制
SPI例子
SPI的全名为Service Provider Interface。先写下例子更好理解:
public interface Coder { void code(); }
public class JavaCoder implements Coder { public void code() { System.out.println("code in JAVA"); } }
public class GoCoder implements Coder { public void code() { System.out.println("code in GO"); } }
META-INF/services下创建文件名:com.dubbo.mylearncode.spi.code(即Coder接口的全路径名)
com.dubbo.mylearncode.spi.JavaCoder com.dubbo.mylearncode.spi.GoCoder
public class Main { public static void main(String[] args){ ServiceLoader<Coder> serviceLoader = ServiceLoader.load(Coder.class); for(Coder coder : serviceLoader){ coder.code(); } } }
执行Main类,输出的结果为:
code in JAVA code in GO
通过ServiceLoader.load(Coder.class)方法,java会到META-INF/services目录下寻找Coder接口的全路径名的文件,即com.dubbo.mylearncode.spi.code,找到文件后会自动加载配置的实现类,从而能够实现可插拔的接口实现定制。
SPI的缺点
- JDK标准的SPI会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
- 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK标准的ScriptEngine,通过 getName()获取脚本类型的名称,但如果RubyScriptEngine因为所依赖的jruby.jar不存在,导致RubyScriptEngine类加载失败,这个失败原因被吃掉了,和ruby对应不起来,当用户执行ruby脚本时,会报不支持ruby,而不是真正失败的原因。
- 增加了对扩展点IoC和AOP的支持,一个扩展点可以直接setter注入其它扩展点。
为了解决这个问题,dubbo实现了自己的一套spi机制,即ExtensionLoader
ExtensionLoader
ExtensionLoader的用法不做即录,直接记录阅读源码的过程。
缓存注释(类属性)
缓存类型 | 缓存名 | 作用 |
---|---|---|
ConcurrentMap<Class<?>, String> | cachedNames | 非@Adaptive的实现类的名字缓存 class -> 扩展点名字(即配置文件中的key) |
Holder<Map<String, Class<?>>> | cachedClasses | 非@Adaptive的实现类的名字缓存,与上面相反 |
Map<String, Activate> | cachedActivates | @Activate的实现类缓存 名字 -> Activate对象 |
Class<?> | cachedAdaptiveClass | @Adaptive的实现类类缓存(从代码中看出,只允许一个Adaptive实现类) |
Holder<Object> | cachedAdaptiveInstance | @Adaptive注解的实例缓存 |
ConcurrentMap<String, Holder<Object>> | cachedInstances | 非@Adaptive实现类的实例缓存 |
String | cachedDefaultName | 默认扩展的名字,即@SPI的value值 |
入口
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)) { //如果无SPI注解 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; }
ExtensionLoader的主入口是一个静态方法getExtensionLoader,接受一个class类型的参数,代表着要获取哪个接口的扩展点。ExtensionLoader是通过@SPI注解来标记一个接口是否可以使用ExtensionLoader获取扩展,所以在指定接口没有@SPI注解时会直接抛出异常。
EXTENSION_LOADERS是一个static的全局静态缓存,存着class与ExtensionLoader的对应map,所以先会在缓存里找对应的ExtensionLoader,如果找不到,就new一个ExtensionLoader并加入缓存中。下面看看new ExtensionLoader
构造方法
private ExtensionLoader(Class type) { this.type = type; if(type == ExtensionFactory.class){ objectFactory = null; }else{ objectFactory = ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension(); } }
进入私有构造方法后,主要对两个属性进行了赋值。type,即ExtensionLoader的泛型类型;objectFactory为ExtensionFactory类型,之后在动态注入时会用到。
主要方法
private static final ExtensionLoader<Container> loader = ExtensionLoader.getExtensionLoader(Container.class);
上面一行代码是在com.alibaba.dubbo.container.Main获取ExtensionLoader的代码,在这行代码执行之后,loader中的关于扩展点的缓存还尚未加载,那缓存是什么时候加载的呢?ExtensionLoader针对spi机制进行了扩展,只有在真正用到的时候才会进行加载。
T getExtension(String name) 根据name获取类实例对象
getExtension(name) //在cachedInstances缓存中找,找不到进入下面 -> createExtension(name) -> getExtensionClasses() //在cachedClasses缓存中找,找不到进入下面 -> loadExtensionClasses() ->loadFile(extensionClasses,filePath) //读取文件,填充缓存 -> injectExtension(T instance)
loadFile()
loadFile是ExtensionLoader加载spi配置文件的方法,是一个比较重要的方法,不只是在getExtension(String name)方法中用到,在所有类似的方法(例如:getExtensionClass(String name),getDefaultExtensionName()等)缓存还未初始化时都会调用。
加载配置文件时,dubbo会从三个位置去找文件,即:
- META-INF/services/
- META-INF/dubbo/
- META-INF/services/internal/ 但是dubbo默认都将配置文件放到了META-INF/services/internal/路径下面
下面是loadFile方法中解析class的其中较关键的部分:
if (clazz.isAnnotationPresent(Adaptive.class)) { //如果该实现类有@Adaptive注解 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 { //如果该实现类无@Adaptive注解 try { clazz.getConstructor(type); //查看该实现类是否有type的构造方法,没有则直接进入异常 //如果有type类型的构造方法,则为wrapper类。加入wrapper缓存 Set<Class<?>> wrappers = cachedWrapperClasses; if (wrappers == null) { cachedWrapperClasses = new ConcurrentHashSet<Class<?>>(); wrappers = cachedWrapperClasses; } wrappers.add(clazz); } catch (NoSuchMethodException e) { //没有type类型的构造方法 clazz.getConstructor(); if (name == null || name.length() == 0) { name = findAnnotationName(clazz); if (name == null || name.length() == 0) { if (clazz.getSimpleName().length() > type.getSimpleName().length() && clazz.getSimpleName().endsWith(type.getSimpleName())) { name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase(); } else { throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + url); } } } 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()); } } } }
可以看出,实现类被分成两种类型进行了解析。有@Adaptive注解、无@Adaptive注解。而无@Adaptive注解又分为两种,Wrapper与普通实现类,wrapper即对普通的实现类又进行了一层包装,必要特征即:构造方法有一个当前类的参数。
- 如果实现类有@Adaptive注解,则直接将cachedAdaptiveClass赋值。(如果之前有值且与当前值不相等,直接抛异常)
- 如果为wrapper类,则加入cachedWrapperClasses缓存
- 其余的即为普通实现类,则加入cachedNames缓存。另,如果有@Active注解的,则加入cachedActivates缓存。(@Active注解还不太明白,后续看代码看到的时候继续补充)
- 最后loadFile方法结束后,会将name-class的缓存加入cachedClasses(getExtensionClasses()方法中)
另:如果是有多个Wrapper实现类时,是会进行多层包装的。例如Protocol接口,可能最外层是 ProtocolFilterWrapper 包装了 ProtocolListenerWrapper ,而 ProtocolListenWrapper 里面才包装着具体的实现类(例如 DubboProtocol )。
createExtension(String name) EXtensionLoader的实例化
之前说到SPI的其中一个缺点就是一次会实例化所有的配置类。而在createExtension(String name)方法中可以看出,ExtensionLoader在使用一个类的时候才会去实例化这个类。
T instance = (T) EXTENSION_INSTANCES.get(clazz); if (instance == null) { EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } injectExtension(instance); //注入实例属性 Set<Class<?>> wrapperClasses = cachedWrapperClasses; //如果有wrapper,为wrapper,否则直接返回。后面的wrapper会覆盖前面的wrapper if (wrapperClasses != null && wrapperClasses.size() > 0) { for (Class<?> wrapperClass : wrapperClasses) { instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } } return instance;
方法首先会到EXTENSION_INSTANCES缓存中取得对象实例,如果没有,则直接利用反射创建一个新对象,并进行属性的注入。如果有wrapper,则创建wrapper对象。通过Wrapper类可以把所有扩展点公共逻辑移至Wrapper中。新加的Wrapper在所有的扩展点上添加了逻辑,有些类似AOP(Wraper代理了扩展点) (目前对wrapper理解还不是特别深刻,后续看代码继续补充)
injectExtension(T instance) ExtensionLoader的自动注入
for (Method method : instance.getClass().getMethods()) { if (method.getName().startsWith("set") && method.getParameterTypes().length == 1 && Modifier.isPublic(method.getModifiers())) { //寻找setter方法 Class<?> pt = method.getParameterTypes()[0]; try { String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : ""; Object object = objectFactory.getExtension(pt, property); if (object != null) { method.invoke(instance, object); } } catch (Exception e) { logger.error("fail to inject via method " + method.getName() + " of interface " + type.getName() + ": " + e.getMessage(), e); } } }
ExtensionLoader会寻找符合条件的setter方法,并利用objectFactory获取对象进行注入。这里获取的对象是通过getAdaptiveExtension()方法获取的自适应对象。@Adaptive我会单独记录一篇文章。
其他常用方法(非Adaptive、Active)
- T getDefaultExtension() 获取默认的扩展。
- String getDefaultExtensionName() 获取默认扩展名,即SPI注解中配置的值
- boolean hasExtension(String name) 是否有指定的扩展
- Set
getSupportedExtensions() 获取所有支持的扩展