继夫的玩弄H辣文的小说|女人与拘性猛交视频|精品欧美高清不卡高清|一起做亏亏的事情的视频|啦啦啦在线视频观看|望月直播下载ios版本|国产日韩欧美一区二区三区

SPI:Java的高可擴展利器

本文分享自華為云社區《一文講透Java核心技術之高可擴展利器SPI-云社區-華為云》,作者: 冰 河 。
SPI的概念
JAVA SPI = 基于接口的編程+策略模式+配置文件 的動態加載機制
SPI的使用場景
Java是一種面向對象語言,雖然Java8開始支持函數式編程和,但是總體來說,還是面向對象的語言 。在使用Java進行面向對象開發時,一般會推薦使用基于接口的編程,程序的模塊與模塊之前不會直接進行實現類的硬編碼 。而在實際的開發過程中,往往一個接口會有多個實現類,各實現類要么實現的邏輯不同,要么使用的方式不同,還有的就是實現的技術不同 。為了使調用方在調用接口的時候 , 明確的知道自己調用的是接口的哪個實現類,或者說為了實現在模塊裝配的時候不用在程序里動態指明 , 這就需要一種服務發現機制 。Java中的SPI加載機制能夠滿足這樣的需求 , 它能夠自動尋找某個接口的實現類 。
大量的框架使用了Java的SPI技術,如下:
(1)JDBC加載不同類型的數據庫驅動
(2)日志門面接口實現類加載,SLF4J加載不同提供商的日志實現類
(3)中大量使用了SPI
(4)Dubbo里面有很多個組件,每個組件在框架中都是以接口的形成抽象出來!具體的實現又分很多種,在程序執行時根據用戶的配置來按需取接口的實現
SPI的使用
當服務的提供者java中xml怎么提示,提供了接口的一種實現后,需要在Jar包的**META-INF//**目錄下,創建一個以接口的名稱(包名.接口名的形式)命名的文件,在文件中配置接口的實現類(完整的包名+類名) 。
當外部程序通過java.util.類裝載這個接口時,就能夠通過該Jar包的**META//**目錄里的配置文件找到具體的實現類名,裝載實例化,完成注入 。同時java中xml怎么提示,SPI的規范規定了接口的實現類必須有一個無參構造方法 。
SPI中查找接口的實現類是通過java.util.,而在java.util.類中有一行代碼如下:
// 加載具體實現類信息的前綴,也就是以接口命名的文件需要放到Jar包中的META-INF/services/目錄下private static final String PREFIX = "META-INF/services/";
這也就是說,我們必須將接口的配置文件寫到Jar包的**META//**目錄下 。
SPI實例
這里,給出一個簡單的SPI使用實例,演示在Java程序中如何使用SPI動態加載接口的實現類 。
注意:實例是基于Java8進行開發的 。
1.創建Maven項目
在IDEA中創建Maven項目spi-demo , 如下:
2.編輯pom.xml
spi-demo io.binghe.spijar1.0.0-SNAPSHOT4.0.0org.springframework.bootspring-boot-maven-pluginorg.apache.maven.pluginsmaven-compiler-plugin3.6.01.81.8
3.創建類加載工具類
在io..spi.包下創建,類中直接調用JDK的類加載Class 。代碼如下所示 。
package io.binghe.spi.loader;import java.util.ServiceLoader;/** * @author binghe * @version 1.0.0 * @description 類加載工具 */public class MyServiceLoader {/*** 使用SPI機制加載所有的Class*/public staticServiceLoader loadAll(final Class clazz) {return ServiceLoader.load(clazz);}}
4.創建接口
在io..spi.包下創建接口,作為測試接口,接口中只有一個方法,打印傳入的字符串信息 。代碼如下所示:
package io.binghe.spi.service;/** * @author binghe * @version 1.0.0 * @description 定義接口 */public interface MyService {/***打印信息*/void print(String info);}
5.創建接口的實現類(1)創建第一個實現類
在io..spi..impl包下創建類,實現接口 。代碼如下所示:
package io.binghe.spi.service.impl;import io.binghe.spi.service.MyService;/** * @author binghe * @version 1.0.0 * @description 接口的第一個實現 */public class MyServiceA implements MyService {@Overridepublic void print(String info) {System.out.println(MyServiceA.class.getName() + " print " + info);}}
(2)創建第二個實現類
在io..spi..impl包下創建類,實現接口 。代碼如下所示:
package io.binghe.spi.service.impl;import io.binghe.spi.service.MyService;/** * @author binghe * @version 1.0.0 * @description 接口第二個實現 */public class MyServiceB implements MyService {@Overridepublic void print(String info) {System.out.println(MyServiceB.class.getName() + " print " + info);}}
6.創建接口文件
在項目的src/main/目錄下創建**META//**目錄,在目錄中創建io..spi..文件,注意:文件必須是接口的全名,之后將實現接口的類配置到文件中,如下所示:
io.binghe.spi.service.impl.MyServiceAio.binghe.spi.service.impl.MyServiceB
7.創建測試類
在項目的io..spi.main包下創建Main類,該類為測試程序的入口類 , 提供一個main()方法 , 在main()方法中調用類加載接口的實現類 。并通過Java8的將結果打印出來 , 如下所示:
package io.binghe.spi.main;import io.binghe.spi.loader.MyServiceLoader;import io.binghe.spi.service.MyService;import java.util.ServiceLoader;import java.util.stream.StreamSupport;/** * @author binghe * @version 1.0.0 * @description 測試的main方法 */public class Main {public static void main(String[] args){ServiceLoader loader = MyServiceLoader.loadAll(MyService.class);StreamSupport.stream(loader.spliterator(), false).forEach(s -> s.print("Hello World"));}}
8.測試實例
運行Main類中的main()方法,打印出的信息如下所示:
io.binghe.spi.service.impl.MyServiceA print Hello Worldio.binghe.spi.service.impl.MyServiceB print Hello WorldProcess finished with exit code 0
通過打印信息可以看出,通過Java SPI機制正確加載出接口的實現類,并調用接口的實現方法 。
源碼解析
這里 , 主要是對SPI的加載流程涉及到的java.util.的源碼的解析 。
進入java.util.的源碼 , 可以看到類實現了java.lang.接口 , 如下所示 。
public final class ServiceLoaderimplements Iterable
說明類是可以遍歷迭代的 。
java.util.類中定義了如下的成員變量:
// 加載具體實現類信息的前綴,也就是以接口命名的文件需要放到Jar包中的META-INF/services/目錄下private static final String PREFIX = "META-INF/services/";// 需要加載的接口private final Class service;// 類加載器,用于加載以接口命名的文件中配置的接口的實現類private final ClassLoader loader;// 創建ServiceLoader時采用的訪問控制上下文環境private final AccessControlContext acc;// 用來緩存已經加載的接口實現類,其中,Key是接口實現類的完整類名 , Value為實現類對象private LinkedHashMap providers = new LinkedHashMap();// 用于延遲加載實現類的迭代器private LazyIterator lookupIterator;
可以看到類中定義了加載前綴為“META-INF//” , 所以,接口文件必須要在項目的src/main/目錄下的**META-INF//**目錄下創建 。
從類調用**.load(clazz)**方法進入源碼,如下所示:
//根據類的Class對象加載指定的類,返回ServiceLoader對象public staticServiceLoader load(Class service) { //獲取當前線程的類加載器 ClassLoader cl = Thread.currentThread().getContextClassLoader(); //動態加載指定的類 , 將類加載到ServiceLoader中 return ServiceLoader.load(service, cl);}
方法中調用了**.load(, cl)**方法,繼續跟蹤代碼,如下所示:
//通過ClassLoader加載指定類的Class,并將返回結果封裝到ServiceLoader對象中public staticServiceLoader load(Class service, ClassLoader loader){ return new ServiceLoader(service, loader);}
可以看到**.load(, cl)**方法中,調用了類的構造方法,繼續跟進代碼 , 如下所示:
//構造ServiceLoader對象private ServiceLoader(Class svc, ClassLoader cl) { //如果傳入的Class對象為空 , 則判處空指針異常 service = Objects.requireNonNull(svc, "Service interface cannot be null"); //如果傳入的ClassLoader為空,則通過ClassLoader.getSystemClassLoader()獲取 , 否則直接使用傳入的ClassLoader loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; reload();}
繼續跟**()**方法,如下所示 。
//重新加載public void reload() { //清空保存加載的實現類的LinkedHashMap providers.clear(); //構造延遲加載的迭代器 lookupIterator = new LazyIterator(service, loader);}
繼續跟進懶加載迭代器的構造函數,如下所示 。
private LazyIterator(Class service, ClassLoader loader) { this.service = service; this.loader = loader;}
可以看到,會將需要加載的接口的Class對象和類加載器賦值給的成員變量 。
當我們在程序中迭代獲取對象實例時,首先在成員變量**中查找是否有緩存的實例對象 。如果存在則直接返回,否則調用**延遲加載迭代器進行加載 。
迭代器進行邏輯判斷的代碼如下所示:
//迭代ServiceLoader的方法public Iterator iterator() { return new Iterator() {//獲取保存實現類的LinkedHashMap的迭代器Iterator<Map.Entry> knownProviders = providers.entrySet().iterator();//判斷是否有下一個元素public boolean hasNext() {//如果knownProviders存在元素,則直接返回trueif (knownProviders.hasNext())return true;//返回延遲加載器是否存在元素return lookupIterator.hasNext();}//獲取下一個元素public S next() {//如果knownProviders存在元素 , 則直接獲取if (knownProviders.hasNext())return knownProviders.next().getValue();//獲取延遲迭代器lookupIterator中的元素return lookupIterator.next();}public void remove() {throw new UnsupportedOperationException();} };}
加載類的流程如下代碼所示
//判斷是否擁有下一個實例private boolean hasNextService() { //如果擁有下一個實例,直接返回true if (nextName != null) {return true; } //如果實現類的全名為null if (configs == null) {try {//獲取全文件名 , 文件相對路徑+文件名稱(包名+接口名)String fullName = PREFIX + service.getName();//類加載器為空,則通過ClassLoader.getSystemResources()方法獲取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()) {//如果configs中沒有更過的元素,則直接返回falseif (!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 {//加載類對象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 {//通過c.newInstance()生成對象實例S p = service.cast(c.newInstance());//將生成的對象實例保存到緩存中(LinkedHashMap)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 action = new PrivilegedAction() {public Boolean run() { return hasNextService(); }};return AccessController.doPrivileged(action, acc); }}public S next() { if (acc == null) {return nextService(); } else {PrivilegedAction action = new PrivilegedAction() {public S run() { return nextService(); }};return AccessController.doPrivileged(action, acc); }}
最后,給出整個java.util.的類,如下所示:
package java.util;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.net.URL;import java.security.AccessControlContext;import java.security.AccessController;import java.security.PrivilegedAction;public final class ServiceLoaderimplements Iterable {// 加載具體實現類信息的前綴,也就是以接口命名的文件需要放到Jar包中的META-INF/services/目錄下private static final String PREFIX = "META-INF/services/";// 需要加載的接口private final Class service;// 類加載器,用于加載以接口命名的文件中配置的接口的實現類private final ClassLoader loader;// 創建ServiceLoader時采用的訪問控制上下文環境private final AccessControlContext acc;// 用來緩存已經加載的接口實現類,其中,Key是接口實現類的完整類名,Value為實現類對象private LinkedHashMap providers = new LinkedHashMap();// 用于延遲加載實現類的迭代器private LazyIterator lookupIterator;//重新加載public void reload() {//清空保存加載的實現類的LinkedHashMapproviders.clear();//構造延遲加載的迭代器lookupIterator = new LazyIterator(service, loader);}//構造ServiceLoader對象private ServiceLoader(Class svc, ClassLoader cl) {//如果傳入的Class對象為空,則判處空指針異常service = Objects.requireNonNull(svc, "Service interface cannot be null");//如果傳入的ClassLoader為空,則通過ClassLoader.getSystemClassLoader()獲?。?裨蛑苯郵褂么?氳腃lassLoaderloader = (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 names)throws IOException, ServiceConfigurationError{String ln = r.readLine();if (ln == null) {return -1;}int ci = ln.indexOf('#');if (ci >= 0) ln = ln.substring(0, ci);ln = ln.trim();int n = ln.length();if (n != 0) {if ((ln.indexOf(' ') >= 0) || (ln.indexOf('t') >= 0))fail(service, u, lc, "Illegal configuration-file syntax");int cp = ln.codePointAt(0);if (!Character.isJavaIdentifierStart(cp))fail(service, u, lc, "Illegal provider-class name: " + ln);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 parse(Class service, URL u)throws ServiceConfigurationError{InputStream in = null;BufferedReader r = null;ArrayList 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 lookuploadprivate class LazyIteratorimplements Iterator{Class service;ClassLoader loader;Enumeration configs = null;Iterator pending = null;String nextName = null;private LazyIterator(Class service, ClassLoader loader) {this.service = service;this.loader = loader;}//判斷是否擁有下一個實例private boolean hasNextService() {//如果擁有下一個實例 , 直接返回trueif (nextName != null) {return true;}//如果實現類的全名為nullif (configs == null) {try {//獲取全文件名,文件相對路徑+文件名稱(包名+接口名)String fullName = PREFIX + service.getName();//類加載器為空,則通過ClassLoader.getSystemResources()方法獲取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()) {//如果configs中沒有更過的元素,則直接返回falseif (!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 {//加載類對象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 {//通過c.newInstance()生成對象實例S p = service.cast(c.newInstance());//將生成的對象實例保存到緩存中(LinkedHashMap)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 action = new PrivilegedAction() {public Boolean run() { return hasNextService(); }};return AccessController.doPrivileged(action, acc);}}public S next() {if (acc == null) {return nextService();} else {PrivilegedAction action = new PrivilegedAction() {public S run() { return nextService(); }};return AccessController.doPrivileged(action, acc);}}public void remove() {throw new UnsupportedOperationException();}}//迭代ServiceLoader的方法public Iterator iterator() {return new Iterator() {//獲取保存實現類的LinkedHashMap的迭代器Iterator<Map.Entry> knownProviders = providers.entrySet().iterator();//判斷是否有下一個元素public boolean hasNext() {//如果knownProviders存在元素,則直接返回trueif (knownProviders.hasNext())return true;//返回延遲加載器是否存在元素return lookupIterator.hasNext();}//獲取下一個元素public S next() {//如果knownProviders存在元素,則直接獲取if (knownProviders.hasNext())return knownProviders.next().getValue();//獲取延遲迭代器lookupIterator中的元素return lookupIterator.next();}public void remove() {throw new UnsupportedOperationException();}};}//通過ClassLoader加載指定類的Class,并將返回結果封裝到ServiceLoader對象中public staticServiceLoader load(Class service,ClassLoader loader){return new ServiceLoader(service, loader);}//根據類的Class對象加載指定的類,返回ServiceLoader對象public staticServiceLoader load(Class service) {//獲取當前線程的類加載器ClassLoader cl = Thread.currentThread().getContextClassLoader();//動態加載指定的類,將類加載到ServiceLoader中return ServiceLoader.load(service, cl);}public staticServiceLoader loadInstalled(Class service) {ClassLoader cl = ClassLoader.getSystemClassLoader();ClassLoader prev = null;while (cl != null) {prev = cl;cl = cl.getParent();}return ServiceLoader.load(service, prev);}/*** Returns a string describing this service.** @returnA descriptive string*/public String toString() {return "java.util.ServiceLoader[" + service.getName() + "]";}}
SPI總結
最后,對Java提供的SPI機制進行簡單的總結 。
優點:
能夠實現項目解耦,使得第三方服務模塊的裝配控制的邏輯與調用者的業務代碼分離 , 而不是耦合在一起 。應用程序可以根據實際業務情況啟用框架擴展或替換框架組件 。
缺點:
參考:深入理解Java中的spi機制
點擊下方,第一時間了解華為云新鮮技術~
華為云博客_大數據博客_AI博客_云計算博客_開發者中心-華為云
【SPI:Java的高可擴展利器】本文到此結束,希望對大家有所幫助 。