2.1 jdk-spi的實現原理
阿新 • • 發佈:2017-10-01
sco ear 核心 pack 註意 接口 lookup snap 清空緩存
dubbo-spi是在jdk-spi的基礎上進行重寫優化,下面看一下jdk-spi。
一、作用
- 為接口自動尋找實現類。
二、實現方式
- 標準制定者制定接口
- 不同廠商編寫針對於該接口的實現類,並在jar的“classpath:META-INF/services/全接口名稱”文件中指定相應的實現類全類名
- 開發者直接引入相應的jar,就可以實現為接口自動尋找實現類的功能
三、使用方法
註意:示例以Log體系為例,但是實際中的Log體系並不是這樣來實現的。
1、pom.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <projectxmlns="http://maven.apache.org/POM/4.0.0" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 5 <modelVersion>4.0.0</modelVersion> 6 <groupId>com.hulk</groupId> 7 <artifactId>java-spi</artifactId> 8 <version>1.0-SNAPSHOT</version> 9 </project>
2、標準接口:com.hulk.javaspi.Log
1 package com.hulk.javaspi; 2 3 public interface Log { 4 void execute(); 5 }
3、具體實現1:com.hulk.javaspi.Log4j
1 package com.hulk.javaspi; 2 3 public class Log4j implementsLog { 4 @Override 5 public void execute() { 6 System.out.println("log4j ..."); 7 } 8 }
4、具體實現2:com.hulk.javaspi.Logback
1 package com.hulk.javaspi; 2 3 public class Logback implements Log { 4 @Override 5 public void execute() { 6 System.out.println("logback ..."); 7 } 8 }
5、指定使用的實現文件:META-INF/services/com.hulk.javaspi.Log
1 com.hulk.javaspi.Logback
註意
- 這裏指定了實現類Logback,那麽加載的時候就會自動為Log接口指定實現類為Logback。
- 這裏也可以指定兩個實現類,那麽在實際中使用哪一個實現類,就需要使用額外的手段來控制。
1 com.hulk.javaspi.Logback 2 com.hulk.javaspi.Log4j
6、加載實現主類:com.hulk.javaspi.Main
1 package com.hulk.javaspi; 2 3 import java.util.Iterator; 4 import java.util.ServiceLoader; 5 6 public class Main { 7 public static void main(String[] args) { 8 ServiceLoader<Log> serviceLoader = ServiceLoader.load(Log.class); 9 Iterator<Log> iterator = serviceLoader.iterator(); 10 while (iterator.hasNext()) { 11 Log log = iterator.next(); 12 log.execute(); 13 } 14 } 15 }
註意:
- ServiceLoader不是實例化以後,就去讀取配置文件中的具體實現,並進行實例化。而是等到使用叠代器去遍歷的時候,才會加載對應的配置文件去解析,調用hasNext方法的時候會去加載配置文件進行解析,調用next方法的時候進行實例化並緩存 - 具體見“源碼分析”
現在來解析Main的源碼。
四、源碼解析
1、獲取ServiceLoader
1 ServiceLoader<Log> serviceLoader = ServiceLoader.load(Log.class);
源碼:
首先來看一下ServiceLoader的6個屬性
1 private static final String PREFIX = "META-INF/services/";//定義實現類的接口文件所在的目錄 2 private final Class<S> service;//接口 3 private final ClassLoader loader;//定位、加載、實例化實現類 4 private final AccessControlContext acc;//權限控制上下文 5 private LinkedHashMap<String,S> providers = new LinkedHashMap<>();//以初始化的順序緩存<接口全名稱, 實現類實例> 6 private LazyIterator lookupIterator;//真正進行叠代的叠代器
其中LazyIterator是ServiceLoader的一個內部類,在叠代部分會說。
1 public static <S> ServiceLoader<S> load(Class<S> service) { 2 ClassLoader cl = Thread.currentThread().getContextClassLoader(); 3 return ServiceLoader.load(service, cl); 4 } 5 6 public static <S> ServiceLoader<S> load(Class<S> service, 7 ClassLoader loader) { 8 return new ServiceLoader<>(service, loader); 9 } 10 11 private ServiceLoader(Class<S> svc, ClassLoader cl) { 12 service = Objects.requireNonNull(svc, "Service interface cannot be null"); 13 loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; 14 acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; 15 reload(); 16 } 17 18 public void reload() { 19 providers.clear();//清空緩存 20 lookupIterator = new LazyIterator(service, loader); 21 }
這樣一個ServiceLoader實例就創建成功了。在創建的過程中,我們看到還實例化了一個LazyIterator,該類下邊會說。
2、獲取叠代器並叠代
1 Iterator<Log> iterator = serviceLoader.iterator(); 2 while (iterator.hasNext()) { 3 Log log = iterator.next(); 4 log.execute(); 5 }
外層叠代器:
1 public Iterator<S> iterator() { 2 return new Iterator<S>() { 3 4 Iterator<Map.Entry<String,S>> knownProviders 5 = providers.entrySet().iterator(); 6 7 public boolean hasNext() { 8 if (knownProviders.hasNext()) 9 return true; 10 return lookupIterator.hasNext(); 11 } 12 13 public S next() { 14 if (knownProviders.hasNext()) 15 return knownProviders.next().getValue(); 16 return lookupIterator.next(); 17 } 18 19 public void remove() { 20 throw new UnsupportedOperationException(); 21 } 22 23 }; 24 }
從查找過程hasNext()和叠代過程next()來看。
- hasNext():先從provider(緩存)中查找,如果有,直接返回true;如果沒有,通過LazyIterator來進行查找。
- next():先從provider(緩存)中直接獲取,如果有,直接返回實現類對象實例;如果沒有,通過LazyIterator來進行獲取。
下面來看一下,LazyIterator這個類。首先看一下他的屬性:
1 Class<S> service;//接口 2 ClassLoader loader;//類加載器 3 Enumeration<URL> configs = null;//存放配置文件 4 Iterator<String> pending = null;//存放配置文件中的內容,並存儲為ArrayList,即存儲多個實現類名稱 5 String nextName = null;//當前處理的實現類名稱
其中,service和loader在上述實例化ServiceLoader的時候就已經實例化好了。
下面看一下hasNext():
1 public boolean hasNext() { 2 if (acc == null) { 3 return hasNextService(); 4 } else { 5 PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() { 6 public Boolean run() { return hasNextService(); } 7 }; 8 return AccessController.doPrivileged(action, acc); 9 } 10 } 11 12 private boolean hasNextService() { 13 if (nextName != null) { 14 return true; 15 } 16 if (configs == null) { 17 try { 18 String fullName = PREFIX + service.getName(); 19 if (loader == null) 20 configs = ClassLoader.getSystemResources(fullName); 21 else 22 configs = loader.getResources(fullName); 23 } catch (IOException x) { 24 fail(service, "Error locating configuration files", x); 25 } 26 } 27 while ((pending == null) || !pending.hasNext()) { 28 if (!configs.hasMoreElements()) { 29 return false; 30 } 31 pending = parse(service, configs.nextElement()); 32 } 33 nextName = pending.next(); 34 return true; 35 }
hasNextService()中,核心實現如下:
- 首先使用loader加載配置文件,此時找到了META-INF/services/com.hulk.javaspi.Log文件;
- 然後解析這個配置文件,並將各個實現類名稱存儲在pending的ArrayList中; --> 此時[ com.hulk.javaspi.Logback ]
- 最後指定nextName; --> 此時nextName=com.hulk.javaspi.Logback
下面看一下next():
1 public S next() { 2 if (acc == null) { 3 return nextService(); 4 } else { 5 PrivilegedAction<S> action = new PrivilegedAction<S>() { 6 public S run() { return nextService(); } 7 }; 8 return AccessController.doPrivileged(action, acc); 9 } 10 } 11 12 private S nextService() { 13 if (!hasNextService()) 14 throw new NoSuchElementException(); 15 String cn = nextName; 16 nextName = null; 17 Class<?> c = null; 18 try { 19 c = Class.forName(cn, false, loader); 20 } catch (ClassNotFoundException x) { 21 fail(service, 22 "Provider " + cn + " not found"); 23 } 24 if (!service.isAssignableFrom(c)) { 25 fail(service, 26 "Provider " + cn + " not a subtype"); 27 } 28 try { 29 S p = service.cast(c.newInstance()); 30 providers.put(cn, p); 31 return p; 32 } catch (Throwable x) { 33 fail(service, 34 "Provider " + cn + " could not be instantiated", 35 x); 36 } 37 throw new Error(); // This cannot happen 38 }
nextService()中,核心實現如下:
- 首先加載nextName代表的類Class,這裏為com.hulk.javaspi.Logback;
- 之後創建該類的實例,並轉型為所需的接口類型
- 最後存儲在provider中,供後續查找,最後返回轉型後的實現類實例。
再next()之後,拿到實現類實例後,就可以執行其具體的方法了。
五、缺點
- 查找一個具體的實現需要遍歷查找,耗時;-->此時就體現出Collection相較於Map差的地方,map可以直接根據key來獲取具體的實現 (dubbo-spi實現了根據key獲取具體實現的方式)
2.1 jdk-spi的實現原理