Java&Android開發-淺析ServiceLoader類

卓修武-發表於2019-03-03

1.SPI的概念

瞭解ServiceLoader,需要先了解 SPI(Service Provider Interface)

SPI的簡單來說就是在程式設計時將一個功能服務的介面與實現分離,在程式執行時通過JVM機制自動找到服務介面的實現類並建立,以達到解耦的目的,提高程式的可擴充性; 比如JDBC

2.ServiceLoader

ServiceLoader就是 Java平臺提供的一個簡單的 Service Provder Framework。使用ServiceLoader有簡單的以下幾個步驟

  • 建立服務介面
  • 在服務介面的實現模組中,建立一個實現類實現對應的服務介面,並通過在專案的resource/META-INF/services資料夾下面建立一個對應該服務介面全限定名的文字檔案,在該文字檔案寫入該服務介面實現類的全限定名,以此達到一個註冊服務的作用(專案打包後在jar檔案裡也得存在該檔案)
  • 服務呼叫方(需求方)通過ServiceLoader類的load方法載入服務並得到服務的實現類

2.1 一個簡單ServiceLoader場景例項

這裡以一個簡單虛擬支付場景為例。
有一個業務模組目前需要使用支付服務,所以我們首先建立了一個PaymenService抽象介面表示 支付服務,介面類中有一個抽象方法**pay(String productName,double price)**表示支付某個商品
###建立服務實現模組

package com.knight.serviceimpl;

import com.knight.PaymentService;

public class PaymentServiceImpl implements PaymentService {
    @Override
    public void pay(String productName, double price) {
        System.out.println("支付模組:購買產品 "+productName +",價格"+price);
    }
}

複製程式碼

在IDEA中的結構如下

image

建立服務介面類

image

通過ServiceLoader獲取服務

業務模組中直接通過ServiceLoader類及PaymentService介面獲取服務例項並實現業務邏輯(業務模組一般是不包含服務的實現模組的)

package com.knight.business;

import com.knight.PaymentService;

import java.util.Iterator;
import java.util.ServiceLoader;

public class BusinessModule {
    public static void run(){
        Iterator<PaymentService> serviceIterator = ServiceLoader.load(PaymentService.class).iterator();
        if (serviceIterator.hasNext()){
            PaymentService paymentService = serviceIterator.next();
            paymentService.pay("Q幣充值",100.00);
        }else {
            System.out.println("未找到支付模組");
        }
    }
}

複製程式碼

以上的核心程式碼是 通過 ServiceLoader的load方法,傳入PaymentService介面類,會返回一個ServiceLoader的例項物件,通過該物件的**iterator()**方法會返回一個 Iterator 的迭代器,可以通過這個迭代器得到所有PaymentService的實現物件。

最後 我們再建立一個app模組執行業務程式碼邏輯,app模組包含service、service-impl、business、3個模組。

image

以上所有程式碼已上傳 git

ServiceLoader 核心原始碼簡單解析

ServiceLoader內部細節
1.首先通過靜態方法load獲取對應服務介面的ServiceLoader例項;
2.ServiceLoader類繼承了Iterabale介面,內部實現了一個服務例項懶載入的迭代器;迭代器內部通過classLoader讀 取對應META-INF/service/資料夾下的服務配置檔案獲取到所有的實現類類名稱,當通過iterator()方法獲取迭代器後,就可以依次例項化service的實現並將實現物件加入到快取中。
3.解析配置檔案的過程就是按行讀取,每一行的文字都是一個服務實現類的全限定名,獲取到類名就可以通過反射例項化物件了

public final class ServiceLoader<S>
    implements Iterable<S>
{
    //Service配置檔案的資源路徑
    private static final String PREFIX = "META-INF/services/";

    // The class or interface representing the service being loaded
    private final Class<S> service;

    // 負責service配置資源載入,例項化service 
    private final ClassLoader loader;
    
        // 服務例項的快取,已被迭代被建立過的會加到這個cache中
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
    
        private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        reload();
    }
    //過載load, 刪除快取並重新例項化iterator
        public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }
    
    //懶載入的service迭代器
        private class LazyIterator
        implements Iterator<S>
    {

        Class<S> service;
        ClassLoader loader;
        Enumeration<URL> configs = null;
        Iterator<String> pending = null;
        String nextName = null;

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

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

        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
        }

        public boolean hasNext() {
            return hasNextService();
        }

        public S next() {
            return nextService();
        }


    }
    //按行讀取檔案,每一行都是服務實現類的介面名
    private Iterator<String> parse(Class<?> service, URL u)throws ServiceConfigurationError
        {
            InputStream in = null;
            BufferedReader r = null;
            ArrayList<String> 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();
        }
}
複製程式碼

Google autoService

以上 當註冊服務實現時如果需要手動建立檔案並寫入服務實現類名稱 難免有些繁瑣,我們可以使用谷歌提供的 AutoService 庫簡化這一過程

使用方式

  1. gradle 引入autoService
dependencies {
    compileOnly `com.google.auto.service:auto-service:1.0-rc2`
    annotationProcessor `com.google.auto.service:auto-service:1.0-rc2`
}
複製程式碼
  1. 服務實現類上加上@AutoService註解,引數為服務抽象類
package com.knight.serviceimpl;

import com.google.auto.service.AutoService;
import com.knight.PaymentService;

@AutoService(PaymentService.class)
public class PaymentServiceImpl implements PaymentService {
    @Override
    public void pay(String productName, double price) {
        System.out.println("支付模組:購買產品 "+productName +",價格"+price);
    }
}

複製程式碼

3.專案編譯觸發auto-service註解處理過程後自動生成了配置檔案

image

其他

該例項原始碼已上傳 git倉庫

相關文章