ServiceLoader實現原理

在java中根據一個子類獲取其父類或接口信息很是方便,可是根據一個接口獲取該接口的全部實現類卻沒那麼容易。java

 

有一種比較笨的辦法就是掃描classpath全部的class與jar包中的class,而後用ClassLoader加載進來,而後再判斷是不是給定接口的子類。可是很顯然,不會使用這種方法,代價太大。apache

java自己也提供了一種方式來獲取一個接口的子類,那就是使用java.util.ServiceLoader#load(java.lang.Class<S>) 方法,可是直接使用該方法也是不能獲取到給定接口全部的子類的。數組

須要接口的子類以配置的方式主動註冊到一個接口上,才能使用ServiceLoader進行加載到子類,而且子類須要有一個無參構造方法,用於被ServiceLoader進行實例化app

 

下面介紹使用ServiceLoader的步驟

一、 編寫Serviceide

package com.mogujie.uni.sl;
/**
 * Created by laibao
 */
public interface Animal {
        void eat();
}   

二、編寫實現類(注意:實現類不必定要與接口在同一個工程中,能夠存在於其餘的jar包中)測試

package com.mogujie.uni.sl;
/**
 * Created by laibao
 */
public class Pig implements Animal {
    @Override
    public void eat() {
        System.out.println("Pig eating...");
    }
}
package com.mogujie.uni.sl;
/**
 * Created by laibao
 */
public class Dog implements Animal {
    @Override
    public void eat() {
        System.out.println("Dog eating...");
    }
}

三、 在實現類所在的工程的classpath下面的創建META-INF/services目錄,該目錄是固定的,必定要按照規定的名稱去建立,該目錄用於配置接口與實現類的映射關係 
而後根據接口全名 在該目錄建立一個文件,例如上面例子中接口全名是com.mogujie.uni.sl.Animal,那麼就須要在實現類的工程中創建META-INF/services/com.mogujie.uni.sl.Animal這樣一個文件,而後在該文件中配置該接口的實現類,若是該接口有多個實現類,一行寫一個(以換行符分割),例如:spa

com.mogujie.uni.sl.Pig
com.mogujie.uni.sl.Dog

四、接下來就能使用ServiceLoader的方法獲取com.mogujie.uni.sl.Animal接口的全部子類了。測試類以下:.net

package com.mogujie.uni;
import com.mogujie.uni.sl.Animal;
import java.util.Iterator;
import java.util.ServiceLoader;
/**
 * Created by laibao
 */
public class TestServiceLoader {
    public static void main(String[] args) {
        ServiceLoader<Animal> serviceLoader = ServiceLoader.load(Animal.class);
        Iterator<Animal> animalIterator = serviceLoader.iterator();
        while(animalIterator.hasNext()){
            Animal animal = animalIterator.next();
            animal.eat();
        }
    }
}

輸出以下:設計

Pig eating...
Dog eating...



ServiceLoader的原理其實很簡單,就是根據給定的參數(接口)就能定位到該接口與實現類的映射配置文件的路徑了,而後讀取該配置文件,就能獲取到該接口的子類code

下面本身實現一個CustomServiceLoader與系統的ServiceLoader具備一樣的功能

package com.mogujie.uni;

import org.apache.commons.io.IOUtils;
import java.net.URL;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;

/**
 * Created by laibao
 */
public class CustomServiceLoader {

    public static final String MAPPING_CONFIG_PREFIX = "META-INF/services";

    public static <S> List<S> loade(Class<S> service) throws Exception{
        String mappingConfigFile = MAPPING_CONFIG_PREFIX + "/" + service.getName() ;
        //因爲一個接口的實現類可能存在多個jar包中的META-INF目錄下,因此下面使用getResources返回一個URL數組
        Enumeration<URL> configFileUrls =  CustomServiceLoader.class.getClassLoader().getResources(mappingConfigFile);
        if(configFileUrls == null){
            return null ;
        }
        List<S> services = new LinkedList<S>();
        while(configFileUrls.hasMoreElements()){
            URL configFileUrl = configFileUrls.nextElement();
            String configContent = IOUtils.toString(configFileUrl.openStream());
            String[] serviceNames = configContent.split("\n");
            for(String serviceName : serviceNames){
                Class serviceClass = CustomServiceLoader.class.getClassLoader().loadClass(serviceName);
                Object serviceInstance = serviceClass.newInstance();
                services.add((S)serviceInstance);
            }
        }
        return services ;
    }

}

測試類以下:

package com.mogujie.uni;
import com.mogujie.uni.sl.Animal;
import java.util.List;
/**
 * Created by laibao
 */
public class CustomServiceLoaderTest {
    public static void main(String[] args) throws Exception {
        List<Animal> animals = CustomServiceLoader.loade(Animal.class);
        for (Animal animal : animals){
            animal.eat();
        }
    }
}

輸出:

Pig eating...
Dog eating...

java系統定義的ServiceLoader與咱們自定義的CustomServiceLoader的loade方法,它們的返回值類型是不同的,ServiceLoader的loade方法返回的是ServiceLoader對象,ServiceLoader對象實現了Iterable接口,經過ServiceLoader的成員方法iterator();就能遍歷全部的服務實例,而咱們自定義的CustomServiceLoader的load方法返回的是一個List對象,直接將全部的服務實例封裝在一個集合裏面返回了。 系統的ServiceLoader經過返回一個Iterator對象可以作到對服務實例的懶加載 只有當調用iterator.next()方法時纔會實例化下一個服務實例,只有須要使用的時候才進行實例化,具體實現讀者能夠去閱讀源碼進行研究,這也是其設計的亮點之一。

相關文章
相關標籤/搜索