JDK SPI

你曾是少年就是我發表於2019-03-19

簡介

 SPI全名為Service Provider Interface
 作用:為介面自動尋找實現類
 實現方式:
  1.標準制定者制定介面
  2.不同廠商編寫針對於該介面的實現類,
  並在jar的“classpath:META-INF/services/全介面名稱”
  檔案中指定相應的實現類全類名
  3.開發者直接引入相應的jar,就可以實現為介面自動尋找實現類的功能

程式碼示例

Log介面

public interface Log {
    void execute();
}
複製程式碼

Log4j類

public class Log4j implements Log {
    @Override
    public void execute() {
        System.out.println("log4j...");
    }
}
複製程式碼

Logback類

public class Logback implements Log {
    @Override
    public void execute() {
        System.out.println("logback ...");
    }
}
複製程式碼

Main類

public class Main {
    public static void main(String[] args) {
        ServiceLoader<Log> serviceLoader = ServiceLoader.load(Log.class);
        Iterator<Log> iterator = serviceLoader.iterator();
        while (iterator.hasNext()) {
            Log log = iterator.next();
            log.execute();
        }
    }
}
複製程式碼
在resources下建立META-INF/services目錄
並建立Log全路徑名的檔案com.ye.spi.Log
檔案裡寫實現類全路徑,可以寫多個
寫多個時,可以自己實現邏輯選擇對應實現
com.ye.spi.Log4j

直接啟動Main類就可以得到想要的效果

原始碼解析

public class Main {
    public static void main(String[] args) {
        ServiceLoader<Log> serviceLoader = ServiceLoader.load(Log.class);
        Iterator<Log> iterator = serviceLoader.iterator();
        while (iterator.hasNext()) {
            Log log = iterator.next();
            log.execute();
        }
    }
}
複製程式碼
1. ServiceLoader.load()
2. serviceLoader.iterator()
3. iterator.hasNext()
4. iterator.next()

第一個方法是ServiceLoader.load()

 先看ServiceLoader的屬性
    private static final String PREFIX="META-INF/services/";//定義實現類的介面檔案所在的目錄
    private final Class service;//介面
    private final ClassLoader loader;//定位、載入、例項化實現類
    private final AccessControlContext acc;//許可權控制上下文
    private LinkedHashMap providers = new LinkedHashMap<>();//以初始化的順序快取<介面全名稱, 實現類例項>
    private LazyIterator lookupIterator;//真正進行迭代的迭代器
public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
    {
        return new ServiceLoader<>(service, loader);
    }
複製程式碼
    看上面兩段程式碼,load方法最終是呼叫傳參的建構函式new ServiceLoader<>(service, loader)
    再繼續看new ServiceLoader<>(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();
    }
public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }
private LazyIterator(Class<S> service, ClassLoader loader) {
            this.service = service;
            this.loader = loader;
        }    
複製程式碼
1. 賦值ServiceLoader的service
2. 賦值ServiceLoader的loader
3. 呼叫reload方法
4. 清空ServiceLoade的providers
5. new LazyIterator物件
綜上所述,ServiceLoader.load()其實就是初始化了ServiceLoader物件和LazyIterator物件

第二個方法是serviceLoader.iterator()

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();
            }

        };
    }
複製程式碼
   獲取迭代器,
   初始化knownProviders,第一次是空的
   實現了hasNext方法和next方法

iterator.hasNext()

public boolean hasNext() {
        if (knownProviders.hasNext())
            return true;
        return lookupIterator.hasNext();
    }
複製程式碼
先判斷knownProviders有沒有,如果有就返回true
如果沒有呼叫lookupIterator.hasNext()
可以認為knownProviders是一個快取,後面會看到什麼時候有值的
先看一下lookupIterator即LazyIterator這個類的屬性
    Class service;//介面
    ClassLoader loader;//類載入器
    Enumeration configs = null;//存放配置檔案
    Iterator pending = null;//存放配置檔案中的內容,並儲存為ArrayList,即儲存多個實現類名稱
    String nextName = null;//當前處理的實現類名稱

LazyIterator.hasNext()方法

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);
            }
        }

private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    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;
        }
複製程式碼
其實呼叫的是LazyIterator.hasNextService()方法
1. nextName如果有,直接返回true
2. 找到對應目錄檔案
3. 解析檔案內容
4. 賦值給nextName
5. 返回true

LazyIterator.next()

public S next() {
                if (knownProviders.hasNext())
                    return knownProviders.next().getValue();
                return lookupIterator.next();
            }
         
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);
            }
        }  
        
 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 {
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error();          // This cannot happen
        }        
複製程式碼
1. 先判斷knownProviders有沒有,如果有就直接取
2. 呼叫nextService獲取
3. 通過nextName載入對應的類,即例項化
4. 把例項化的物件放入providers中,下次就可以在這裡面取,不用在例項化了
5. 返回例項化的物件

小結:spi大致流程就講完了

1. 初始化 ServiceLoader和LazyIterator
2. 通過load傳入的介面找到對應目錄的檔案
3. 解析檔案找到類全路徑名
4. 通過全路徑名載入例項化類
5. 放入快取並返回該物件

相關文章