企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
## Java SPI SPI的全名为Service Provider Interface, java spi就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。 SPI的使用是寻找服务实现,例如,我们在项目A中定义了接口Developer,之后可以使用ServiceLoader加载Developer的实现类,实现类可能有B项目或C项目实现。 A项目: ``` package com.shisj.study.dubbo.spi.Developer; public interface Developer { public String getPrograme(); } ``` B项目中提供了两种实现类: ``` package com.shisj.study.dubbo.spi.impl; public class JavaDeveloper implements Developer{ public String getPrograme() { return "Java"; } } ``` ``` package com.shisj.study.dubbo.spi.impl; public class PythonDeveloper implements Developer{ public String getPrograme() { return "Python"; } } ``` 在B项目的classpath下创建META-INF/services文件夹,内部包含com.shisj.study.dubbo.spi.Developer 文件,这是接口的全名。里面的内容是该借接口的实现类; ``` com.shisj.study.dubbo.spi.impl.JavaDeveloper com.shisj.study.dubbo.spi.impl.PythonDeveloper ``` 将B项目打包成jar包并引用至A项目中,然后我们就可以通过ServiceLoader查找实现类, ``` public ServiceLoader<Developer> serviceloader = ServiceLoader.load(Developer.class); for (Developer dev : serviceloader) { System.out.println("out." + dev.getPrograme()); } // out.Java // out.Python ``` ## Dubbo扩展 dubbo扩展点加载从JDK标准的SPI(Service Provider Interface)扩展点发现机制加强而来。具体的配置文件在classpath的/META-INF/dubbo/internal文件夹里面。dubbo的扩展机制与spi不同,其文件中的内容记录了`key=class`,原因是: >当扩展点的static字段或方法签名上引用了三方库, 如果三方库不存在,会导致类初始化失败, Extension标识Dubbo就拿不到了,异常信息就和配置对应不起来。 比如: Extension("mina")加载失败, 当用户配置使用mina时,就会报找不到扩展点, 而不是报加载扩展点失败,以及失败原因。 ### ExtensionLoader 在dubbo源码中,对可扩展点的加载都是通过ExtensionLoader类获取实例的,例如: ``` // 获取Compiler接口的实现类 ExtensionLoader<Compiler> loader1 = ExtensionLoader.getExtensionLoader(Compiler.class); Compiler compile1 = loader1.getAdaptiveExtension(); System.out.println(compile1.getClass()); Compiler compile2 = loader1.getExtension("jdk"); System.out.println(compile2.getClass()); ``` 这样做的目的是扩展性强,使用者可以无侵入的方式自己进行实现接口供dubbo使用。 ExtensionLoader类内部保存着所有接口的实现类,并记录了缺省Adaptive实现类和缺省的扩展类,我们可以通过属性大致了解ExtensionLoader的功能。下面是实例属性: ``` private final Class<?> type; // ExtensionLoader 创建时指定的类型 private final ExtensionFactory objectFactory; // ExtensionLoader的扩展工厂 private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>(); // 记录了实现类的class与名称的对应关系 private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();// 保存map,内部保存了实现类name与实现类class的对应关系 private final Map<String, Activate> cachedActivates = new ConcurrentHashMap<String, Activate>(); //保存有@Activate注解的实现类 private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>(); //保存实现类name与实例 private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>(); //缺省的实例 private volatile Class<?> cachedAdaptiveClass = null; // 缺省的实现类 private String cachedDefaultName; // 默认的实现类名称,@SPI中指定 private volatile Throwable createAdaptiveInstanceError; // 创建是的错误 private Set<Class<?>> cachedWrapperClasses; private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<String, IllegalStateException>(); ``` 除此之外,ExtensionLoader类内部的静态属性保存所有扩展类与ExtensionLoader、实例的映射。 ``` private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>(); private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>(); ``` **加载过程** 首先根据class从EXTENSION_LOADERS查找对应的ExtensionLoader,如果为null则创建一个ExtensionLoader,指定其type为要查找的class。此时虽然获取了ExtensionLoader,但还未查找接口的实现类,调用getExtension()或getAdaptiveExtension()等方法时才会真正的查找;getExtensionClasses()方法会依次查找下面3个路径的文件,读取内容获得`name=class`键值对,并保存到相应的属性中。 ``` META-INF/services/ META-INF/dubbo/ META-INF/dubbo/internal/ ``` 下面是解析文件并保存的过程: * cachedDefaultName 如果ExtensionLoader的type有SPI注解且设置了value,那么这个value就是该接口默认的实现类名称cachedDefaultName,例如`Compiler`接口使用了注解`@SPI("javassist")`,那么Compiler的ExtensionLoader的cachedDefaultName为javassist。cachedDefaultName的目的是getDefaultExtension()获取默认的实现类,或查找不到@Adaptiv的实现类时,使用cachedDefaultName创建Adaptive的实现类。 * 按照顺序从3个文件夹中,读取以type的全名命名的文件,解析name和class,如果实现类有@Adaptive注解,设置cachedAdaptiveClass为该实现类; * 如果没有@Adaptive,首先判断实现类是否有将要查找的接口作为参数的构造方法,如果有添加到cachedWrapperClasses变量中, * 如果没有这种构造方法,判断是否有@Activate注解,有的话保存到cachedActivates中;cachedNames保存所有的class和name的映射关系 * 最终,将name与实现类class的映射保存在cachedClasses中。 至此,ExtensionLoader针对给的的接口,查找了所有的扩展,并取得了默认实现类的名称,Adaptive实现类class(可能没有,后续获取是dubbo会创建),以及name与实现类class的映射关系。 有了这些信息,在createExtension(name)时就会根据name获得class然后newInstance获取实例。 ## dubbo扩展测试 我们自己实现一个MyCompiler,其实现了Compiler接口 ``` public class MyCompiler implements Compiler { public Class<?> compile(String code, ClassLoader classLoader) { return new JavassistCompiler().compile(code, classLoader); } } ``` 在/META-INF/services/com.alibaba.dubbo.common.compiler.Compiler文件中加入下面的映射 ``` mycp=com.shisj.study.dubbo.loader.MyCompiler ``` 测试中,可以通过mycp获取到实现类MyCompiler ``` Compiler compile2 = loader1.getExtension("mycp"); System.out.println(compile2.getClass());//class com.shisj.study.dubbo.loader.MyCompiler ``` **getAdaptiveExtension内部逻辑** getAdaptiveExtension首先会查找接口具有@Adaptive注解的实现类作为Adaptive类,如果没有dubbo会动态创建一个新的类,内部会从url取参数,如果没有则取默认的值,来自于接口的@SPI注解,如`@SPI("javassist")`的值。 **getExtension()** 获得Adaptive类的实例后,会将其传入cachedWrapperClasses,包装为Wrapper类