简述网站建设的基本思路,网站空间的后台控制面板,如何提高网站百度权重,网站上面的内容里面放照片怎么做的Java SPI 的实现原理并不复杂#xff0c;它的实现基于 Java 类加载机制和反射机制。 当使用 ServiceLoader.load(ClassT service) 方法加载服务时#xff0c;会检查 META-INF/services 目录下是否存在以接口全限定名命名的文件。如果存在#xff0c;则读取文件内容它的实现基于 Java 类加载机制和反射机制。 当使用 ServiceLoader.load(ClassT service) 方法加载服务时会检查 META-INF/services 目录下是否存在以接口全限定名命名的文件。如果存在则读取文件内容获取实现该接口的类的全限定名并通过 Class.forName() 方法加载对应的类。
以下是我的项目中基于SPI加载接口实例的代码 /*** SPI(Service Provider Interface)机制加载 {link IRowMetaData}所有实例* return 表名和 {link RowMetaData}实例的映射对象 */private static HashMapString, RowMetaData loadRowMetaData() { ServiceLoaderIRowMetaData providers ServiceLoader.load(IRowMetaData.class);IteratorIRowMetaData itor providers.iterator();IRowMetaData instance;HashMapString, RowMetaData m new HashMap();while(itor.hasNext()){try {instance itor.next(); } catch (ServiceConfigurationError e) {// 实例初始化失败输出错误日志后继续循环SimpleLog.log(e.getMessage());continue;}if(instance instanceof RowMetaData){RowMetaData rowMetaData (RowMetaData)instance;m.put(rowMetaData.tablename, rowMetaData);}}return m;}这个代码写了几年了在Windows,Linux,Android平台都没有任何问题。但最近在客户的设备上(Android)出了问题。 我们提供的DEMO程序在客户的设备上也能正常执行但放在客户的项目代码中就不行。 通过跟踪发现itor.hasNext()返回false,就导致没有执行循环应该是没有找到META-INF/services 目录下接口IRowMetaData的全限定名命名的文件。
进一步跟踪找到itor.hasNext()【java.util.ServiceLoader.LayIterator.hasNext()】最终调用的实现代码 private boolean hasNextService() {if (nextName ! null) {return true;}if (configs null) {try {String fullName PREFIX service.getName();if (loader null)configs ClassLoader.getSystemResources(fullName);elseconfigs 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;}可以看到最终是通过调用ClassLoader.getResources(String)来读取META-INF/services 目录下接口类的全限定名命名的文件根据ClassLoader.getResources(String)的方法说明如果没有找到对应的文件则返回空的对象。
所以itor.hasNext()返回false的直接原因应该就是没找到META-INF/services 目录下接口类的全限定名命名的文件。但是为什么会这样没搞明白。 进一步看ServiceLoader的代码才知道ServiceLoader.load其实有两个重载方法如下 public static S ServiceLoaderS load(ClassS service,ClassLoader loader){return new ServiceLoader(service, loader);}public static S ServiceLoaderS load(ClassS service) {ClassLoader cl Thread.currentThread().getContextClassLoader();return ServiceLoader.load(service, cl);}常用的ServiceLoader.load(ClassT service)方法的实现其实是使用当前线程上下文的ClassLoader实例为参数调用了ServiceLoader.load(ClassT service,ClassLoader loader)方法
于是我尝试改为调用ServiceLoader.load(ClassT service,ClassLoader loader)方法,以接口类的ClassLoader实例作为loader参数如下
ServiceLoaderIRowMetaData providers ServiceLoader.load(IRowMetaData.class,IRowMetaData.class.getClassLoader());居然可以正常执行了。
为什么会这样太奇怪了我无法解释最有可能是的原因是客户的项目中的使用某种应用框架有自己的ClassLoader作为当前线程上下文的ClassLoader实例.这个ClassLoader有BUG,找不到META-INF/services 目录下的资源文件。
不管怎样为了安全起见兼容两种加载方式我将获取 实例迭代器(Iterator)的逻辑修改为如下 即优先调用ServiceLoader.load(ClassT service)如果返回空则调用ServiceLoader.load(ClassT service,ClassLoader loader)方法 /*** SPI(Service Provider Interface)机制加载 {link IRowMetaData}所有实例* return 表名和 {link RowMetaData}实例的映射对象 */private static HashMapString, RowMetaData loadRowMetaData() { // 优先调用ServiceLoader.load(ClassT service)// 如果返回空则调用ServiceLoader.load(ClassT service,ClassLoader loader)方法IteratorIRowMetaData itor ServiceLoader.load(IRowMetaData.class).iterator();if(!itor.hasNext()){itor ServiceLoader.load(IRowMetaData.class,IRowMetaData.class.getClassLoader()).iterator();}IRowMetaData instance;HashMapString, RowMetaData m new HashMap();while(itor.hasNext()){try {instance itor.next(); } catch (ServiceConfigurationError e) {// 实例初始化失败输出错误日志后继续循环SimpleLog.log(e.getMessage());continue;}if(instance instanceof RowMetaData){RowMetaData rowMetaData (RowMetaData)instance;m.put(rowMetaData.tablename, rowMetaData);}}return m;}这个问题解决了但感觉是莫名奇妙我查了很多关于第三方库及JDK的源码关于SPI的使用方式都是调用ServiceLoader.load(ClassT service)很少有特别强制要调用ServiceLoader.load(ClassT service,ClassLoader loader)方法大家都是这么用的到我这里却出了毛病很难向客户证明这不是我们的问题。 好在可以通过修改我们代码增加容错性逻辑解决了问题。