电脑知识|欧美黑人一区二区三区|软件|欧美黑人一级爽快片淫片高清|系统|欧美黑人狂野猛交老妇|数据库|服务器|编程开发|网络运营|知识问答|技术教程文章 - 好吧啦网

您的位置:首頁技術文章
文章詳情頁

詳解java實踐SPI機制及淺析源碼

瀏覽:3日期:2022-08-28 14:13:47

1.概念

正式步入今天的核心內容之前,溪源先給大家介紹一下關于SPI機制的相關概念,最后會提供實踐源代碼。

SPI即Service Provider Interface,屬于JDK內置的一種動態的服務提供發現機制,可以理解為運行時動態加載接口的實現類。更甚至,大家可以將SPI機制與設計模式中的策略模式建立聯系。

SPI機制:

詳解java實踐SPI機制及淺析源碼

從上圖中理解SPI機制:標準化接口+策略模式+配置文件;

SPI機制核心思想:系統設計的各個抽象,往往有很多不同的實現方案,在面向的對象的設計里,一般推薦模塊之間基于接口編程,模塊之間不對實現類進行硬編碼。一旦代碼里涉及具體的實現類,就違反了可拔插的原則,如果需要替換一種實現,就需要修改代碼。為了實現在模塊裝配的時候能不在程序里動態指明,這就需要一種服務發現機制

使用場景:

1.數據庫驅動加載:面對不同廠商的數據庫,JDBC需要加載不同類型的數據庫驅動; 2.日志接口實現:SLF4J加載不同日志實現類; 3.溪源在實際開發中也使用了SPI機制:面對不同儀器平臺的結果文件上傳需要解析具體的結果,文件不同,解析邏輯不同,因此采用SPI機制能夠解耦和降低維護成本;

SPI機制使用約定:

從上面的圖中,我們可以清晰的知道SPI的三部分:接口+實現類+配置文件;因此,項目中若要利用SPI機制,則需要遵循以下約定:

當服務提供者提供了接口的一種具體實現后,在jar包的META-INF/services目錄下創建一個以“接口全限定名”為命名的文件,內容為實現類的全限定名。 主程序通過java.util.ServiceLoder動態裝載實現模塊,它通過掃描META-INF/services目錄下的配置文件找到實現類的全限定名,把類加載到JVM;

注意:除SPI,我還發布了最新Java架構項目實戰教程+大廠面試題庫, 點擊此處免費獲取,小白勿進!

2.實踐

整體包結構如圖:

詳解java實踐SPI機制及淺析源碼

新建標準化接口:

public interface SayService { void say(String word);}

建立兩個實現類

@Servicepublic class ASayServiceImpl implements SayService { @Override public void say(String word) { System.out.println(word + ' A say: I am a boy'); }}@Servicepublic class BSayServiceImpl implements SayService { @Override public void say(String word) { System.out.println(word + ' B say: I am a girl'); }}

新建META-INF/services目錄和配置文件(以接口全限定名)

配置文件內容為實現類全限定名

com.qxy.spi.impl.ASayServiceImplcom.qxy.spi.impl.BSayServiceImpl

單測

@SpringBootTest@RunWith(SpringRunner.class)public class SpiTest { static ServiceLoader<SayService> services = ServiceLoader.load(SayService.class); @Test public void test1() { for (SayService sayService : services) { sayService.say('Hello'); } }}

結果

Hello A say: I am a boyHello B say: I am a girl

3.源碼

源碼主要加載流程如下:

應用程序調用ServiceLoader.load方法 ServiceLoader.load方法內先創建一個新的ServiceLoader,并實例化該類中的成員變量;

loader(ClassLoader類型,類加載器) acc(AccessControlContext類型,訪問控制器) providers(LinkedHashMap<String,S>類型,用于緩存加載成功的類) lookupIterator(實現迭代器功能)

應用程序通過迭代器接口獲取對象實例 ServiceLoader先判斷成員變量providers對象中(LinkedHashMap<String,S>類型)是否有緩存實例對象,如果有緩存,直接返回。如果沒有緩存,執行類的裝載。

讀取META-INF/services/下的配置文件,獲得所有能被實例化的類的名稱,值得注意的是,ServiceLoader可以跨越jar包獲取META-INF下的配置文件; 通過反射方法Class.forName()加載類對象,并用instance()方法將類實例化。 把實例化后的類緩存到providers對象中,(LinkedHashMap<String,S>類型) 然后返回實例對象。

public final class ServiceLoader<S> implements Iterable<S>{ // 加載具體實現類信息的前綴 private static final String PREFIX = 'META-INF/services/'; // 需要加載的接口 // The class or interface representing the service being loaded private final Class<S> service; // 用于加載的類加載器 // The class loader used to locate, load, and instantiate providers private final ClassLoader loader; // 創建ServiceLoader時采用的訪問控制上下文 // The access control context taken when the ServiceLoader is created private final AccessControlContext acc; // 用于緩存已經加載的接口實現類,其中key為實現類的完整類名 // Cached providers, in instantiation order private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); // 用于延遲加載接口的實現類 // The current lazy-lookup iterator private LazyIterator lookupIterator; public void reload() { providers.clear(); lookupIterator = new LazyIterator(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(); } private static void fail(Class<?> service, String msg, Throwable cause) throws ServiceConfigurationError { throw new ServiceConfigurationError(service.getName() + ': ' + msg, cause); } private static void fail(Class<?> service, String msg) throws ServiceConfigurationError { throw new ServiceConfigurationError(service.getName() + ': ' + msg); } private static void fail(Class<?> service, URL u, int line, String msg) throws ServiceConfigurationError { fail(service, u + ':' + line + ': ' + msg); } // Parse a single line from the given configuration file, adding the name // on the line to the names list. //具體解析資源文件中的每一行內容 private int parseLine(Class<?> service, URL u, BufferedReader r, int lc, List<String> names) throws IOException, ServiceConfigurationError { String ln = r.readLine(); if (ln == null) { //-1表示解析完成 return -1; } // 如果存在’#’字符,截取第一個’#’字符串之前的內容,’#’字符之后的屬于注釋內容 int ci = ln.indexOf(’#’); if (ci >= 0) ln = ln.substring(0, ci); ln = ln.trim(); int n = ln.length(); if (n != 0) { //不合法的標識:’ ’、’t’ if ((ln.indexOf(’ ’) >= 0) || (ln.indexOf(’t’) >= 0))fail(service, u, lc, 'Illegal configuration-file syntax'); int cp = ln.codePointAt(0); //判斷第一個 char 是否一個合法的 Java 起始標識符 if (!Character.isJavaIdentifierStart(cp))fail(service, u, lc, 'Illegal provider-class name: ' + ln); //判斷所有其他字符串是否屬于合法的Java標識符 for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {cp = ln.codePointAt(i);if (!Character.isJavaIdentifierPart(cp) && (cp != ’.’)) fail(service, u, lc, 'Illegal provider-class name: ' + ln); } //不存在則緩存 if (!providers.containsKey(ln) && !names.contains(ln))names.add(ln); } return lc + 1; } private Iterator<String> parse(Class<?> service, URL u) throws ServiceConfigurationError { InputStream in = null; BufferedReader r = null; ArrayList<String> names = new ArrayList<>(); try { in = u.openStream(); r = new BufferedReader(new InputStreamReader(in, 'utf-8')); int lc = 1; while ((lc = parseLine(service, u, r, lc, names)) >= 0); } catch (IOException x) { fail(service, 'Error reading configuration file', x); } finally { try {if (r != null) r.close();if (in != null) in.close(); } catch (IOException y) {fail(service, 'Error closing configuration file', y); } } return names.iterator(); } // Private inner class implementing fully-lazy provider lookup // private class LazyIterator implements Iterator<S> { Class<S> service; ClassLoader loader; // 加載資源的URL集合 Enumeration<URL> configs = null; // 需加載的實現類的全限定類名的集合 Iterator<String> pending = null; // 下一個需要加載的實現類的全限定類名 String nextName = null; private LazyIterator(Class<S> service, ClassLoader loader) { this.service = service; this.loader = loader; } private boolean hasNextService() { if (nextName != null) {return true; } if (configs == null) {try {// 資源名稱,META-INF/services + 全限定名 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; } private S nextService() { if (!hasNextService())throw new NoSuchElementException(); String cn = nextName; nextName = null; Class<?> c = null; try { //反射構造Class實例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()); // 實例完成,添加緩存,Key:實現類全限定類名,Value:實現類實例providers.put(cn, p);return p; } catch (Throwable x) {fail(service, 'Provider ' + cn + ' could not be instantiated', x); } throw new Error(); // This cannot happen } public boolean hasNext() { if (acc == null) {return hasNextService(); } else {PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() { public Boolean run() { return hasNextService(); }};return AccessController.doPrivileged(action, acc); } } 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); } } public void remove() { throw new UnsupportedOperationException(); } } public Iterator<S> iterator() { return new Iterator<S>() { Iterator<Map.Entry<String,S>> knownProviders= providers.entrySet().iterator(); public boolean hasNext() {if (knownProviders.hasNext()) return true;return lookupIterator.hasNext(); } public S next() {if (knownProviders.hasNext()) return knownProviders.next().getValue();return lookupIterator.next(); } public void remove() {throw new UnsupportedOperationException(); } }; } public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) { // 返回ServiceLoader的實例 return new ServiceLoader<>(service, loader); } public static <S> ServiceLoader<S> loadInstalled(Class<S> service) { ClassLoader cl = ClassLoader.getSystemClassLoader(); ClassLoader prev = null; while (cl != null) { prev = cl; cl = cl.getParent(); } return ServiceLoader.load(service, prev); } public String toString() { return 'java.util.ServiceLoader[' + service.getName() + ']'; }}

4.總結

SPI機制在實際開發中使用得場景也有很多。特別是統一標準的不同廠商實現,溪源也正是利用SPI機制(但略做改進,避免過多加載資源浪費)實現不同技術平臺的結果文件解析需求。

優點

使用Java SPI機制的優勢是實現解耦,使得第三方服務模塊的裝配控制的邏輯與調用者的業務代碼分離,而不是耦合在一起。應用程序可以根據實際業務情況啟用框架擴展或替換框架組件。

缺點

雖然ServiceLoader也算是使用的延遲加載,但是基本只能通過遍歷全部獲取,也就是接口的實現類全部加載并實例化一遍。如果你并不想用某些實現類,它也被加載并實例化了,這就造成了浪費。

源碼傳送門:SPI Service

到此這篇關于詳解java實踐SPI機制及淺析源碼的文章就介紹到這了,更多相關java SPI機制內容請搜索好吧啦網以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持好吧啦網!

標簽: Java
相關文章:
主站蜘蛛池模板: 企小优-企业数字化转型服务商_网络推广_网络推广公司 | 桑茶-七彩贝壳桑叶茶 长寿茶 | 炉门刀边腹板,焦化设备配件,焦化焦炉设备_沧州瑞创机械制造有限公司 | 无菌实验室规划装修设计-一体化实验室承包-北京洁净净化工程建设施工-北京航天科恩实验室装备工程技术有限公司 | 铝镁锰板厂家_进口钛锌板_铝镁锰波浪板_铝镁锰墙面板_铝镁锰屋面-杭州军晟金属建筑材料 | GEDORE扭力螺丝刀-GORDON防静电刷-CHEMTRONICS吸锡线-上海卓君电子有限公司 | 深圳希玛林顺潮眼科医院(官网)│深圳眼科医院│医保定点│香港希玛林顺潮眼科中心连锁品牌 | 磁棒电感生产厂家-电感器厂家-电感定制-贴片功率电感供应商-棒形电感生产厂家-苏州谷景电子有限公司 | 高柔性拖链电缆-聚氨酯卷筒电缆-柔性屏蔽电缆厂家-玖泰电缆 | 仓储货架_南京货架_钢制托盘_仓储笼_隔离网_环球零件盒_诺力液压车_货架-南京一品仓储设备制造公司 | 全国国际学校排名_国际学校招生入学及学费-学校大全网 | 壹车网 | 第一时间提供新车_资讯_报价_图片_排行! | 口信网(kousing.com) - 行业资讯_行业展会_行业培训_行业资料 | 考试试题_试卷及答案_诗词单词成语 - 优易学| 知企服务-企业综合服务(ZiKeys.com)-品优低价、种类齐全、过程管理透明、速度快捷高效、放心服务,知企专家! | 福建自考_福建自学考试网 | 苏州伊诺尔拆除公司_专业酒店厂房拆除_商场学校拆除_办公楼房屋拆除_家工装拆除拆旧 | 多功能真空滤油机_润滑油全自动滤油机_高效真空滤油机价格-重庆润华通驰 | 硅胶制品-硅橡胶制品-东莞硅胶制品厂家-广东帝博科技有限公司 | 量子管通环-自清洗过滤器-全自动反冲洗过滤器-沼河浸过滤器 | 引领中高档酒店加盟_含舍·美素酒店品牌官网| 医学动画公司-制作3d医学动画视频-医疗医学演示动画制作-医学三维动画制作公司 | 重庆监控_电子围栏设备安装公司_门禁停车场管理系统-劲浪科技公司 | 奥因-光触媒除甲醛公司-除甲醛加盟公司十大品牌 | 电主轴-高速精密电主轴-高速电机厂家-瑞德沃斯品牌有限公司 | 多功能真空滤油机_润滑油全自动滤油机_高效真空滤油机价格-重庆润华通驰 | 开平机_纵剪机厂家_开平机生产厂家|诚信互赢-泰安瑞烨精工机械制造有限公司 | 环氧乙烷灭菌器_压力蒸汽灭菌器_低温等离子过氧化氢灭菌器 _低温蒸汽甲醛灭菌器_清洗工作站_医用干燥柜_灭菌耗材-环氧乙烷灭菌器_脉动真空压力蒸汽灭菌器_低温等离子灭菌设备_河南省三强医疗器械有限责任公司 | 杭州月嫂技术培训服务公司-催乳师培训中心报名费用-产后康复师培训机构-杭州优贝姆健康管理有限公司 | 地图标注-手机导航电子地图如何标注-房地产商场地图标记【DiTuBiaoZhu.net】 | 硫化罐_蒸汽硫化罐_大型硫化罐-山东鑫泰鑫智能装备有限公司 | 牛奶检测仪-乳成分分析仪-北京海谊 | 自动部分收集器,进口无油隔膜真空泵,SPME固相微萃取头-上海楚定分析仪器有限公司 | 铜镍-康铜-锰铜-电阻合金-NC003 - 杭州兴宇合金有限公司 | 热缩管切管机-超声波切带机-织带切带机-无纺布切布机-深圳市宸兴业科技有限公司 | 网站建设,北京网站建设,北京网站建设公司,网站系统开发,北京网站制作公司,响应式网站,做网站公司,海淀做网站,朝阳做网站,昌平做网站,建站公司 | 福建成考网-福建成人高考网 | 工业电炉,台车式电炉_厂家-淄博申华工业电炉有限公司 | 课件导航网_ppt课件_课件模板_课件下载_最新课件资源分享发布平台 | 打包钢带,铁皮打包带,烤蓝打包带-高密市金和金属制品厂 | LNG鹤管_内浮盘价格,上装鹤管,装车撬厂家-连云港赛威特机械 |