1. 程式人生 > >2.1 jdk-spi的實現原理

2.1 jdk-spi的實現原理

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 <project 
xmlns="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 implements
Log { 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的實現原理