天津建设网工程信息网站,免费网站源码大全,一个人怎么开发自己的app,德阳哪里有做网站的1、Java的SPI机制
SPI的全称是Service Provider Interface#xff0c;是JDK内置的动态加载实现扩展点的机制#xff0c;通过SPI可以动态获取接口的实现类#xff0c;属于一种设计理念。
系统设计的各个抽象#xff0c;往往有很多不同的实现方案#xff0c;在面向的对象的…1、Java的SPI机制
SPI的全称是Service Provider Interface是JDK内置的动态加载实现扩展点的机制通过SPI可以动态获取接口的实现类属于一种设计理念。
系统设计的各个抽象往往有很多不同的实现方案在面向的对象的设计里一般推荐模块之间基于接口编程模块之间不对实现类进行硬编码。如果代码中引用了特定的实现类那么就违反了可插拔的原则。为了进行实现的替换需要对代码进行修改。需要一种服务发现机制以实现在模块装配时无需在程序中动态指定。最常见的使用案例有JDBC、Dubbo和Spring。
笔者在代码中最常用的就是将策略设计模式SPI进行组合不直接对接口的实现类进行代码的硬编可以根据业务的实际使用选择对应的实现类。
SPI由三个组件构成Service、Service Provider以及ServiceLoader下面是一个简单例子
// 1、使用SPI时先定义好接口
package com.theone.featherpublic interface SendService {String say();
}// 2、创建实现类
package com.theone.feather;public class HelloSendService implements SendService {Overridepublic String say() {return Hello SPI;}
}// 3、在classpath下创建META-INF/services目录添加文件com.theone.feather.SendService
// 文件名要和接口全路径一样, 里面内容填写实现类的全路径,每个路径各占一行// 4、通过ServiceLoader调用
SpringBootApplication
public class TheOneApplication implements CommandLineRunner {public static void main(String[] args) {SpringApplication.run(TheOneApplication.class, args);}Overridepublic void run(String... args) throws Exception {ServiceLoaderSendService loader ServiceLoader.load(SendService.class);for(SendService sendService : loader) {System.out.println(sendService.say());}}
}
从例子也可以看出传统的SPI迭代时会遍历所有的实现需要定制化能够做到根据条件自动加载某个实现。
2、Dubbo的SPI机制
在聊Dubbo的SPI机制的时候需要说下开闭原则开闭原则就是指对扩展开放修改关闭尽量通过扩展软件的模块、类、方法来实现功能的变化而不是通过修改已有的代码来完成。这样做就可以大大降低因为修改代码而给程序带来的出错率。
对于Dubbo而言涉及到服务的RPC调用以及服务治理相关的功能需要抽象出来而对于具体的实现开放出来由用户去根据自身业务需要随意扩展例如下图中各个功能的扩展。 所以在Dubbo中会经常看到下面的逻辑分别是自适应扩展点、指定名称的扩展点、激活扩展点
ExtensionLoader.getExtensionLoader(xxx.class).getAdaptiveExtension();
ExtensionLoader.getExtensionLoader(xxx.class).getExtension(name);
ExtensionLoader.getExtensionLoader(xxx.class).getActivateExtension(url, key);
比如在Protocol的获取中会有
Protocol protocol ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();Dubbo根据你的相关配置找到具体的Protocol实现类并实例化具体的对象。所以总结来说Extension就是指某个功能的扩展实现可能是Dubbo自带的也可能是业务自己实现的。
正常来说我们用SPI都是把接口的实现的全路径写在文件里面解析出来之后再通过反射的方式去实例化这个类然后将这个类放在集合里面存储起来。Dubbo也是这样的操作而且它还是实现了IOC和AOP的功能。IOC 就是说如果这个扩展类依赖其他属性Dubbo 会自动的将这个属性进行注入。这个功能如何实现了一个常见思路是获取这个扩展类的 setter 方法调用 setter 方法进行属性注入。AOP 指的是什么了这个说的是 Dubbo 能够为扩展类注入其包装类。比如 DubboProtocol 是 Protocol 的扩展类ProtocolListenerWrapper 是 DubboProtocol 的包装类。
下文介绍时每个功能会用扩展点指代而对应的实现则是扩展来指代。
3、Dubbo SPI的实现
在了解Dubbo的SPI机制之前有几个注解我们要先了解下
3.1、SPI注解
SPI注解用于标记一个扩展点接口该注解提供了扩展点的默认实现已经作用域。
Documented
Retention(RetentionPolicy.RUNTIME)
Target({ElementType.TYPE})
public interface SPI {/*** 默认扩展点实现名*/String value() default ;/*** 扩展点的作用域*/ExtensionScope scope() default ExtensionScope.APPLICATION;
}Dubbo的除了和java原生的spi一样文件名是接口名之外内容上支持两种类型的格式
// 第一种格式和Java的SPI一样
com.foo.XxxProtocol
com.foo.YyyProtocol// 第二种支持key-value的形式其中key是扩展点实现的名称
xxxcom.foo.XxxProtocol
yyycom.foo.YyyProtocol
zzz,defaultcom.foo.ZzzProtocol
3.2、Activate注解
Activate的作用是用于特定条件下的激活用户通过group和value配置激活条件Activate标记的扩展点实现在满足某个条件时会被会激活并且实例化。也就是自适应扩展类的使用场景比如我们有需求在调用某一个方法时基于参数选择调用到不同的实现类。和工厂方法有些类似基于不同的参数构造出不同的实例对象。 在 Dubbo 中实现的思路和这个差不多不过 Dubbo 的实现更加灵活它的实现和策略模式有些类似。每一种扩展类相当于一种策略基于 URL 消息总线将参数传递给 ExtensionLoader通过 ExtensionLoader 基于参数加载对应的扩展类实现运行时动态调用到目标实例上。
3.3、ExtensionAccessor
扩展访问的最基本类型是ExtensionAccessor它提供了Extension的默认获取实现由接口的default方法指定 public interface ExtensionAccessor {// ExtensionDirector继承自ExtensionAccessor, ExtensionAccessor反过来// 又从ExtensionDirector获取ExtensionLoaderExtensionDirector getExtensionDirector();default T ExtensionLoaderT getExtensionLoader(ClassT type) {return this.getExtensionDirector().getExtensionLoader(type);}default T T getExtension(ClassT type, String name) {ExtensionLoaderT extensionLoader getExtensionLoader(type);return extensionLoader ! null ? extensionLoader.getExtension(name) : null;}default T T getAdaptiveExtension(ClassT type) {ExtensionLoaderT extensionLoader getExtensionLoader(type);return extensionLoader ! null ? extensionLoader.getAdaptiveExtension() : null;}default T T getDefaultExtension(ClassT type) {ExtensionLoaderT extensionLoader getExtensionLoader(type);return extensionLoader ! null ? extensionLoader.getDefaultExtension() : null;}}
上面的ExtensionAccessor塞入ExtensionDirector的方式应该是用了代理设计模式提供了默认的实现。ExtensionAccessor提供了接口的同时也充当着Proxy的角色default关键字使得这种方式得以实现。
3.4、ExtensionDirector
ExtensionAccessor会将请求交给子类ExtensionDirector去处理ExtensionDirector是一个带有作用域的扩展访问器这一点从它带有属性ScopeModel可以看出 public ExtensionDirector(ExtensionDirector parent, ExtensionScope scope, ScopeModel scopeModel) {// 这里传入了父ExtensionDirector类似java的双亲委派机制this.parent parent;// 传递作用域ExtensionScope和ScopeModelthis.scope scope;this.scopeModel scopeModel;}
从ExtensionAccessor调用ExtensionDirector的逻辑可以看到ExtensionDirector会将扩展交给内部的ExtensionLoader去处理 public T ExtensionLoaderT getExtensionLoader(ClassT type) {checkDestroyed();if (type null) {throw new IllegalArgumentException(Extension type null);}if (!type.isInterface()) {throw new IllegalArgumentException(Extension type ( type ) is not an interface!);}if (!withExtensionAnnotation(type)) {throw new IllegalArgumentException(Extension type ( type ) is not an extension, because it is NOT annotated with SPI.class.getSimpleName() !);}// 1. find in local cache// 根据传递的类型从本地缓存中获取ExtensionLoaderT loader (ExtensionLoaderT) extensionLoadersMap.get(type);ExtensionScope scope extensionScopeMap.get(type);// 如果扩展的作用域在本地缓存为空则通过注解SPI尝试去获取if (scope null) {SPI annotation type.getAnnotation(SPI.class);scope annotation.scope();extensionScopeMap.put(type, scope);}// 对于第一次获取的类型可能loader为空如果作用域是仅限自我访问// 这时就需要创建一个针对该类型的extensionLoaderif (loader null scope ExtensionScope.SELF) {// create an instance in self scopeloader createExtensionLoader0(type);}// 2. find in parent// 这里用到了构造函数时传入进来的ExtensionDirector,如果本地找不到就去parent查找if (loader null) {if (this.parent ! null) {loader this.parent.getExtensionLoader(type);}}// 3. create it// 创建针对该类的extensionLoader注意这里是在上面获取parent之后// 所以正常是如果parent不为null的情况这里是和java的双亲委派机制一样的// 有父ExtensionDirector去创建ExtensionLoader并且放在父ExtensionDirector的本地缓存中// 查找时先查找自己本地缓存如果找不到再去父ExtensionDirector去查找if (loader null) {loader createExtensionLoader(type);}return loader;}
ExtensionDirector获取ExtensionLoader的方式参考了Java的双亲委派机制如果本地缓存中查找不到Class对应的ExtensionLoader则是去父ExtensionDirector中查找。如果ExtensionDirector不为空且Scope不是SELF则由父ExtensionDirector去负责创建针对该扩展点类型的ExtensionLoader。
这里也可以看出每一个扩展点对应一个ExtensionLoader。
前面提到了ExtensionDirector是带有作用域ScopeModel的访问器这点在创建ExtensionLoader中有所体现 private T ExtensionLoaderT createExtensionLoader(ClassT type) {ExtensionLoaderT loader null;// 检查扩展点的ScopeModel是否和ExtensionDirector的ScopeMode是否一致if (isScopeMatched(type)) {// if scope is matched, just create itloader createExtensionLoader0(type);}return loader;}private T ExtensionLoaderT createExtensionLoader0(ClassT type) {checkDestroyed();ExtensionLoaderT loader;extensionLoadersMap.putIfAbsent(type, new ExtensionLoaderT(type, this, scopeModel));loader (ExtensionLoaderT) extensionLoadersMap.get(type);return loader;}private boolean isScopeMatched(Class? type) {// 通过注解SPI获取当前扩展点的作用域final SPI defaultAnnotation type.getAnnotation(SPI.class);return defaultAnnotation.scope().equals(scope);}
上面逻辑的意思是只有同个作用域的扩展点才能被ExtensionDirector访问并且创建。但是在getExtensionLoader的时候可以看到对于本地缓存没有的ExtensionLoader是可以去父类中查找并返回的也就是说父ExtensionDirector可以和子ExtensionDirector存在不一样的Scope甚至比子ExtensionDirector更高的作用域。
3.5、ExtensionLoader
类似ClassLoader的作用整个扩展机制的主要逻辑部分其提供了配置的加载、缓存扩展类以及对象生成的工作。
ExtensionLoader的主要入口是通过三个方法调用分别是getExtensiongetAdaptiveExtensionClass以及getActivateExtension三个方法。
3.5.1、getExtension
要记住一点是ExtensionLoader的每个实例只用于一个type所以在初始化的时候会指定当前ExtensionLoader所能处理的type。 ExtensionLoader(Class? type, ExtensionDirector extensionDirector, ScopeModel scopeModel) {// 每一个ExtensionLoader对应一个typethis.type type;...
getExtension是最常用的方法一般是通过指定name获取文件中对应的实现(namecom.xxx)。 public T getExtension(String name) {T extension getExtension(name, true);if (extension null) {throw new IllegalArgumentException(Not find extension: name);}return extension;}public T getExtension(String name, boolean wrap) {checkDestroyed();if (StringUtils.isEmpty(name)) {throw new IllegalArgumentException(Extension name null);}// 如果传递的name为true则返回默认的扩展点实现if (true.equals(name)) {return getDefaultExtension();}String cacheKey name;if (!wrap) {cacheKey _origin;}// ExtensionLoader也会缓存类型的具体实现类似Spring那样的bean单例模式final HolderObject holder getOrCreateHolder(cacheKey);Object instance holder.get();if (instance null) {// 这里用Holder而不是直接用具体扩展点实现的对象的原因是为了避免这里的锁冲突// 一个具体的扩展点实现对应一个Holder这样不同的实现类在创建的时候由于// holder的不同synchronized就不是锁同一个对象这样并发的时候减少锁冲突的作用// 这里也还常见的volatile、双重检查创建单例模式的写法// 我想想这里为啥用holder,其实holder就是给name对应类型的一个锁每个name各自有一个// 如果不这么做的话就必须得有外部一个全局的对象去避免创建对象的线程安全问题// 这样的话会造成并发创建慢synchronized (holder) {instance holder.get();if (instance null) {instance createExtension(name, wrap);holder.set(instance);}}}return (T) instance;}
当然Dubbo肯定是懒加载的机制不会一口气把所有的扩展点实现都实例化只有用到的时候再去创建实力当时类还是会加载的。这里createExtension可以根据具体的name找到并实例化extension。 private T createExtension(String name, boolean wrap) {Class? clazz getExtensionClasses().get(name);if (clazz null || unacceptableExceptions.contains(name)) {throw findException(name);}try {// extensionInstances是缓存的对应类型的实例化对象,这里应该是每个类型一个对象的单例模式T instance (T) extensionInstances.get(clazz);if (instance null) {// createExtensionInstance负责初始化对象extensionInstances.putIfAbsent(clazz, createExtensionInstance(clazz));instance (T) extensionInstances.get(clazz);、// 这里的postBefore和postAfter应该是类似实例前后的埋点操作instance postProcessBeforeInitialization(instance, name);injectExtension(instance);instance postProcessAfterInitialization(instance, name);}// wrap是是否包装的意思类似AOPif (wrap) {ListClass? wrapperClassesList new ArrayList();if (cachedWrapperClasses ! null) {wrapperClassesList.addAll(cachedWrapperClasses);wrapperClassesList.sort(WrapperComparator.COMPARATOR);Collections.reverse(wrapperClassesList);}if (CollectionUtils.isNotEmpty(wrapperClassesList)) {for (Class? wrapperClass : wrapperClassesList) {Wrapper wrapper wrapperClass.getAnnotation(Wrapper.class);boolean match (wrapper null) || ((ArrayUtils.isEmpty(wrapper.matches()) || ArrayUtils.contains(wrapper.matches(),name)) !ArrayUtils.contains(wrapper.mismatches(), name));if (match) {// 将新生成的实例注入到wrapper中不过Wrapper类必须构造函数要有可以加入代理的属性instance injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));instance postProcessAfterInitialization(instance, name);}}}}// Warning: After an instance of Lifecycle is wrapped by cachedWrapperClasses, it may not still be Lifecycle instance, this application may not invoke the lifecycle.initialize hook.initExtension(instance);return instance;} catch (Throwable t) {throw new IllegalStateException(Extension instance (name: name , class: type ) couldnt be instantiated: t.getMessage(),t);}}
第一次实例化的时候扩展点实现的信息全部为空这时需要第一次扫描下文件的内容以及缓存到内存中其中第一步就是获取文件内的扩展点实现的全路径。 private MapString, Class? getExtensionClasses() {// cachedClasses指缓存文件中的扩展点实现已经对应的名字nameMapString, Class? classes cachedClasses.get();if (classes null) {synchronized (cachedClasses) {// 再次获取避免等锁的时候有其他线程获取了classes cachedClasses.get();if (classes null) {try {// 第一次获取时为空,所以遍历文件查询classes loadExtensionClasses();} catch (InterruptedException e) {logger.error(COMMON_ERROR_LOAD_EXTENSION, , ,Exception occurred when loading extension class (interface: type ),e);throw new IllegalStateException(Exception occurred when loading extension class (interface: type ),e);}cachedClasses.set(classes);}}}return classes;}private MapString, Class? loadExtensionClasses() throws InterruptedException {checkDestroyed();// 缓存默认的扩展点实现,这里SPI注解可以指定cacheDefaultExtensionName();MapString, Class? extensionClasses new HashMap();// 分别通过三个不同优先级的扩展点实现获取地方加载for (LoadingStrategy strategy : strategies) {loadDirectory(extensionClasses, strategy, type.getName());// compatible with old ExtensionFactoryif (this.type ExtensionInjector.class) {loadDirectory(extensionClasses, strategy, ExtensionFactory.class.getName());}}return extensionClasses;}private void cacheDefaultExtensionName() {// 用于获取接口中的SPI注解final SPI defaultAnnotation type.getAnnotation(SPI.class);if (defaultAnnotation null) {return;}// 这里的value是获取SPI的value值,表示默认扩展点名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];}}}
Dubbo有三个不同优先级的扩展点实现的加载机制也就是去哪些目录扫描扩展点实现的地方通过LoadingStrategy表示。按优先级大小排序的话分别是
DubboInternalLoadingStrategy(META-INF/dubbo/internal/) DubboLoadingStrategy(META-INF/dubbo/) ServicesLoadingStrategy(META-INF/services/) private void loadDirectory(MapString, Class? extensionClasses, LoadingStrategy strategy,String type) throws InterruptedException {loadDirectoryInternal(extensionClasses, strategy, type);try {String oldType type.replace(org.apache, com.alibaba);if (oldType.equals(type)) {return;}//if class not found,skip try to load resourcesClassUtils.forName(oldType);loadDirectoryInternal(extensionClasses, strategy, oldType);} catch (ClassNotFoundException classNotFoundException) {}}private void loadDirectoryInternal(MapString, Class? extensionClasses,LoadingStrategy loadingStrategy, String type)throws InterruptedException {// 文件名通常是扩展点接口的全路径,这里不同的策略会有不同的文件夹目录,所以加起来就是全路径String fileName loadingStrategy.directory() type;try {ListClassLoader classLoadersToLoad new LinkedList();// try to load from ExtensionLoaders ClassLoader first// 这里是指是否loadingStrategy的classLoader要和ExtensionLoader的一样// 我暂时能想到的是这样加载效率会高一点这样ExtensionLoader和扩展点对应的ClassLoader是同一个// findClass的时候就可以直接找到我猜的if (loadingStrategy.preferExtensionClassLoader()) {ClassLoader extensionLoaderClassLoader ExtensionLoader.class.getClassLoader();if (ClassLoader.getSystemClassLoader() ! extensionLoaderClassLoader) {classLoadersToLoad.add(extensionLoaderClassLoader);}}if (specialSPILoadingStrategyMap.containsKey(type)) {String internalDirectoryType specialSPILoadingStrategyMap.get(type);//skip to load spi when name dont matchif (!LoadingStrategy.ALL.equals(internalDirectoryType) !internalDirectoryType.equals(loadingStrategy.getName())) {return;}classLoadersToLoad.clear();classLoadersToLoad.add(ExtensionLoader.class.getClassLoader());} else {// load from scope modelSetClassLoader classLoaders scopeModel.getClassLoaders();if (CollectionUtils.isEmpty(classLoaders)) {// 直接从类路径下获取这个文件注意这里是systemResource会调用systemClassLoader获取// 那这样看LoadStrategy只是指定了优先级和去哪里找的路径// 注意这里的ClassLoader是去所有的类路径下寻找fileName,可以能不同的包下会有相同名字的目录和文件名// 所以这里会返回多个Enumerationjava.net.URL resources ClassLoader.getSystemResources(fileName);if (resources ! null) {while (resources.hasMoreElements()) {// 这里就是读取文件的每一行然后分析两边的name和实现的全路径,并且加载类到extensionClasses中loadResource(extensionClasses, null, resources.nextElement(),loadingStrategy.overridden(), loadingStrategy.includedPackages(),loadingStrategy.excludedPackages(),loadingStrategy.onlyExtensionClassLoaderPackages());}}} else {classLoadersToLoad.addAll(classLoaders);}}MapClassLoader, Setjava.net.URL resources ClassLoaderResourceLoader.loadResources(fileName, classLoadersToLoad);resources.forEach(((classLoader, urls) - {loadFromClass(extensionClasses, loadingStrategy.overridden(), urls, classLoader,loadingStrategy.includedPackages(), loadingStrategy.excludedPackages(),loadingStrategy.onlyExtensionClassLoaderPackages());}));} catch (InterruptedException e) {throw e;} catch (Throwable t) {logger.error(COMMON_ERROR_LOAD_EXTENSION, , ,Exception occurred when loading extension class (interface: type , description file: fileName ).,t);}}
Dubbo可扩展机制源码解析
SPI 自适应拓展
先贴两篇dubbo官方文档吧等后续有时间再把自适应和激活扩展解析下~ 4、Models提供的隔离级别
Dubbo目前提供了三个级别上的隔离JVM级别、应用级别、服务(模块)级别从而实现各个级别上的生命周期及配置信息的单独管理。这三个层次上的隔离由 FrameworkModel、ApplicationModel 和 ModuleModel 及它们对应的 Config 来完成。 从这个树状图的可以看出来FrameworkModel提供了最顶级的资源配置隔离同个JVM的不同应用可以通过FrameworkModel管理自己的配置和元数据不会互相干扰可以通过某些配置使得不同的java应用使用同一个JVM这里就不介绍了。所以FrameworkModel提供的是JVM级别的配置隔离。
eg假设我们有一个在线教育平台平台下有多个租户而我们希望使这些租户的服务部署在同一个 JVM 上以节省资源但它们之间可能使用不同的注册中心、监控设施、协议等因此我们可以为每个租户分配一个 FrameWorkModel 实例来实现这种隔离。 思考: 为啥要自定义ClassLoader