java的SPI機制

SPI

全稱爲 Service Provider Interface,是一種服務發現機制。咱們看看簡單的一個例子。
定義一個接口HelloSpiService:緩存

public interface HelloSpiService {  
    void sayHello();  
}

實現類HelloSpiServiceImplapp

public class HelloSpiServiceImpl implements HelloSpiService {  
    @Override  
  public void sayHello() {  
        System.out.println("hello");  
    }  
}

在資源META-INF/services文件下建立一個文件com.learn.dubbo.spi.HelloSpiService,文件名同接口全路徑,文件內容就是上面的實現類com.learn.dubbo.spi.impl.HelloSpiServiceImpl,也能夠有多個。
測試代碼:ide

public class Test {  
    public static void main(String[] args) {  
        ServiceLoader<HelloSpiService> load = ServiceLoader.load(HelloSpiService.class);  
        Iterator<HelloSpiService> iterator = load.iterator();  
        while (iterator.hasNext()) {  
            HelloSpiService helloSpiService = iterator.next();  
            helloSpiService.sayHello();  
        }  
    }  
}

運行結果以下:
image.png
Iterator就是文件的實現類內容。源碼分析

源碼分析

load

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)
{
    // 返回一個ServiceLoader
    return 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,Iterator遍歷就是他作的
    lookupIterator = new LazyIterator(service, loader);
}

private LazyIterator(Class<S> service, ClassLoader loader) {  
    this.service = service;  
    this.loader = loader;  
}

iterator

返回一個迭代器,knownProviders是用來儲存解析過的文件的值,也就是說,上面的load.iterator()再執行的時候,此時返回的是已解析過的值。若是沒有解析過,那hasNext()next()是由lookupIterator執行的。測試

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

    };
}

hasNext

第一次解析的時候,knownProviders尚未緩存值,因此包括下面的next,都是lookupIterator的方法。
lookupIterator有5個成員變量:ui

  • service:接口,load的時候初始化
  • loader:類加載器,load的時候初始化
  • configs:接口文件信息
  • pending:資源文件的內容
  • nextName:下一個值
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() {
    // 下一個有值,直接返回true
    if (nextName != null) {
        return true;
    }
    // 配置文件沒有值,就去讀取
    if (configs == null) {
        try {
            // 這個PREFIX就是META-INF/services/,咱們建立文件夾的路徑
            // fullName接口全路徑
            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;
}

主要是讀取文件,把文件內容的實現類的路徑讀取出來。this

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();
    // 把下一個節點賦值給cn,而後再重置nextName
    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 {
        // 判斷實例對象,並把值存入providers,後面再load的時候,就是用providers生成的knownProviders
        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
}

主要是獲取實例化對象,並緩存起來。spa

相關文章
相關標籤/搜索