結(jié)合實戰(zhàn)和源碼來聊聊Java中的SPI機制徽职?

寫在前面

SPI機制能夠非常方便的為某個接口動態(tài)指定其實現(xiàn)類象颖,在某種程度上,這也是某些框架具有高度可擴展性的基礎(chǔ)姆钉。今天说订,我們就從源碼級別深入探討下Java中的SPI機制。

注:文章已收錄到:https://github.com/sunshinelyz/technology-binghe

SPI的概念

SPI在Java中的全稱為Service Provider Interface潮瓶,是JDK內(nèi)置的一種服務(wù)提供發(fā)現(xiàn)機制陶冷,是Java提供的一套用來被第三方實現(xiàn)或者擴展的API,它可以用來啟用框架擴展和替換組件毯辅。

JAVA SPI = 基于接口的編程+策略模式+配置文件的動態(tài)加載機制

SPI的使用場景

Java是一種面向?qū)ο笳Z言埂伦,雖然Java8開始支持函數(shù)式編程和Stream,但是總體來說思恐,還是面向?qū)ο蟮恼Z言沾谜。在使用Java進行面向?qū)ο箝_發(fā)時,一般會推薦使用基于接口的編程胀莹,程序的模塊與模塊之前不會直接進行實現(xiàn)類的硬編碼基跑。而在實際的開發(fā)過程中,往往一個接口會有多個實現(xiàn)類描焰,各實現(xiàn)類要么實現(xiàn)的邏輯不同媳否,要么使用的方式不同,還有的就是實現(xiàn)的技術(shù)不同。為了使調(diào)用方在調(diào)用接口的時候篱竭,明確的知道自己調(diào)用的是接口的哪個實現(xiàn)類力图,或者說為了實現(xiàn)在模塊裝配的時候不用在程序里動態(tài)指明,這就需要一種服務(wù)發(fā)現(xiàn)機制室抽。Java中的SPI加載機制能夠滿足這樣的需求搪哪,它能夠自動尋找某個接口的實現(xiàn)類。

大量的框架使用了Java的SPI技術(shù)坪圾,如下:

(1)JDBC加載不同類型的數(shù)據(jù)庫驅(qū)動
(2)日志門面接口實現(xiàn)類加載晓折,SLF4J加載不同提供商的日志實現(xiàn)類
(3)Spring中大量使用了SPI

  • 對servlet3.0規(guī)范
  • 對ServletContainerInitializer的實現(xiàn)
  • 自動類型轉(zhuǎn)換Type Conversion SPI(Converter SPI、Formatter SPI)等

(4)Dubbo里面有很多個組件兽泄,每個組件在框架中都是以接口的形成抽象出來漓概!具體的實現(xiàn)又分很多種,在程序執(zhí)行時根據(jù)用戶的配置來按需取接口的實現(xiàn)

SPI的使用

當服務(wù)的提供者病梢,提供了接口的一種實現(xiàn)后胃珍,需要在Jar包的META-INF/services/目錄下,創(chuàng)建一個以接口的名稱(包名.接口名的形式)命名的文件蜓陌,在文件中配置接口的實現(xiàn)類(完整的包名+類名)觅彰。

當外部程序通過java.util.ServiceLoader類裝載這個接口時,就能夠通過該Jar包的META/Services/目錄里的配置文件找到具體的實現(xiàn)類名钮热,裝載實例化填抬,完成注入。同時隧期,SPI的規(guī)范規(guī)定了接口的實現(xiàn)類必須有一個無參構(gòu)造方法飒责。

SPI中查找接口的實現(xiàn)類是通過java.util.ServiceLoader,而在java.util.ServiceLoader類中有一行代碼如下:

// 加載具體實現(xiàn)類信息的前綴仆潮,也就是以接口命名的文件需要放到Jar包中的META-INF/services/目錄下
private static final String PREFIX = "META-INF/services/";

這也就是說宏蛉,我們必須將接口的配置文件寫到Jar包的META/Services/目錄下。

SPI實例

這里性置,給出一個簡單的SPI使用實例拾并,演示在Java程序中如何使用SPI動態(tài)加載接口的實現(xiàn)類。

注意:實例是基于Java8進行開發(fā)的鹏浅。

1.創(chuàng)建Maven項目

在IDEA中創(chuàng)建Maven項目spi-demo辟灰,如下:

image

2.編輯pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<artifactId>spi-demo</artifactId>
<groupId>io.binghe.spi</groupId>
<packaging>jar</packaging>
<version>1.0.0-SNAPSHOT</version>
<modelVersion>4.0.0</modelVersion>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.6.0</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
    </plugins>
</build>

</project>

3.創(chuàng)建類加載工具類

在io.binghe.spi.loader包下創(chuàng)建MyServiceLoader,MyServiceLoader類中直接調(diào)用JDK的ServiceLoader類加載Class篡石。代碼如下所示。

package io.binghe.spi.loader;
 
import java.util.ServiceLoader;
 
/**
 * @author binghe
 * @version 1.0.0
 * @description 類加載工具
 */
public class MyServiceLoader {
 
    /**
     * 使用SPI機制加載所有的Class
     */
    public static <S> ServiceLoader<S> loadAll(final Class<S> clazz) {
        return ServiceLoader.load(clazz);
    }
}

4.創(chuàng)建接口

在io.binghe.spi.service包下創(chuàng)建接口MyService西采,作為測試接口凰萨,接口中只有一個方法,打印傳入的字符串信息。代碼如下所示:

package io.binghe.spi.service;
 
/**
 * @author binghe
 * @version 1.0.0
 * @description 定義接口
 */
public interface MyService {
 
    /**
     *  打印信息
     */
    void print(String info);
}

5.創(chuàng)建接口的實現(xiàn)類

(1)創(chuàng)建第一個實現(xiàn)類MyServiceA

在io.binghe.spi.service.impl包下創(chuàng)建MyServiceA類胖眷,實現(xiàn)MyService接口武通。代碼如下所示:

package io.binghe.spi.service.impl;
import io.binghe.spi.service.MyService;
 
/**
 * @author binghe
 * @version 1.0.0
 * @description 接口的第一個實現(xiàn)
 */
public class MyServiceA implements MyService {
    @Override
    public void print(String info) {
        System.out.println(MyServiceA.class.getName() + " print " + info);
    }
}

(2)創(chuàng)建第二個實現(xiàn)類MyServiceB

在io.binghe.spi.service.impl包下創(chuàng)建MyServiceB類,實現(xiàn)MyService接口珊搀。代碼如下所示:

package io.binghe.spi.service.impl;
 
import io.binghe.spi.service.MyService;
 
/**
 * @author binghe
 * @version 1.0.0
 * @description 接口第二個實現(xiàn)
 */
public class MyServiceB implements MyService {
    @Override
    public void print(String info) {
        System.out.println(MyServiceB.class.getName() + " print " + info);
    }
}

6.創(chuàng)建接口文件

在項目的src/main/resources目錄下創(chuàng)建META/Services/目錄冶忱,在目錄中創(chuàng)建io.binghe.spi.service.MyService文件,注意:文件必須是接口MyService的全名境析,之后將實現(xiàn)MyService接口的類配置到文件中囚枪,如下所示:

io.binghe.spi.service.impl.MyServiceA
io.binghe.spi.service.impl.MyServiceB

7.創(chuàng)建測試類

在項目的io.binghe.spi.main包下創(chuàng)建Main類,該類為測試程序的入口類劳淆,提供一個main()方法链沼,在main()方法中調(diào)用ServiceLoader類加載MyService接口的實現(xiàn)類。并通過Java8的Stream將結(jié)果打印出來沛鸵,如下所示:

package io.binghe.spi.main;
 
import io.binghe.spi.loader.MyServiceLoader;
import io.binghe.spi.service.MyService;
 
import java.util.ServiceLoader;
import java.util.stream.StreamSupport;
 
/**
 * @author binghe
 * @version 1.0.0
 * @description 測試的main方法
 */
public class Main {
 
    public static void main(String[] args){
        ServiceLoader<MyService> loader = MyServiceLoader.loadAll(MyService.class);
        StreamSupport.stream(loader.spliterator(), false).forEach(s -> s.print("Hello World"));
    }
}

8.測試實例

運行Main類中的main()方法括勺,打印出的信息如下所示:

io.binghe.spi.service.impl.MyServiceA print Hello World
io.binghe.spi.service.impl.MyServiceB print Hello World

Process finished with exit code 0

通過打印信息可以看出,通過Java SPI機制正確加載出接口的實現(xiàn)類曲掰,并調(diào)用接口的實現(xiàn)方法疾捍。

源碼解析

這里,主要是對SPI的加載流程涉及到的java.util.ServiceLoader的源碼的解析栏妖。

進入java.util.ServiceLoader的源碼乱豆,可以看到ServiceLoader類實現(xiàn)了java.lang.Iterable接口,如下所示底哥。

public final class ServiceLoader<S>  implements Iterable<S> 

說明ServiceLoader類是可以遍歷迭代的咙鞍。

java.util.ServiceLoader類中定義了如下的成員變量:

// 加載具體實現(xiàn)類信息的前綴,也就是以接口命名的文件需要放到Jar包中的META-INF/services/目錄下
private static final String PREFIX = "META-INF/services/";

// 需要加載的接口
private final Class<S> service;

// 類加載器趾徽,用于加載以接口命名的文件中配置的接口的實現(xiàn)類
private final ClassLoader loader;

// 創(chuàng)建ServiceLoader時采用的訪問控制上下文環(huán)境
private final AccessControlContext acc;

// 用來緩存已經(jīng)加載的接口實現(xiàn)類续滋,其中,Key是接口實現(xiàn)類的完整類名孵奶,Value為實現(xiàn)類對象
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

// 用于延遲加載實現(xiàn)類的迭代器
private LazyIterator lookupIterator;

可以看到ServiceLoader類中定義了加載前綴為“META-INF/services/”疲酌,所以,接口文件必須要在項目的src/main/resources目錄下的META-INF/services/目錄下創(chuàng)建了袁。

從MyServiceLoader類調(diào)用ServiceLoader.load(clazz)方法進入源碼朗恳,如下所示:

//根據(jù)類的Class對象加載指定的類,返回ServiceLoader對象
public static <S> ServiceLoader<S> load(Class<S> service) {
    //獲取當前線程的類加載器
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    //動態(tài)加載指定的類载绿,將類加載到ServiceLoader中
    return ServiceLoader.load(service, cl);
}

方法中調(diào)用了ServiceLoader.load(service, cl)方法粥诫,繼續(xù)跟蹤代碼,如下所示:

//通過ClassLoader加載指定類的Class崭庸,并將返回結(jié)果封裝到ServiceLoader對象中
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader){
    return new ServiceLoader<>(service, loader);
}

可以看到ServiceLoader.load(service, cl)方法中怀浆,調(diào)用了ServiceLoader類的構(gòu)造方法谊囚,繼續(xù)跟進代碼,如下所示:

//構(gòu)造ServiceLoader對象
private ServiceLoader(Class<S> svc, ClassLoader cl) {
    //如果傳入的Class對象為空执赡,則判處空指針異常
    service = Objects.requireNonNull(svc, "Service interface cannot be null");
    //如果傳入的ClassLoader為空镰踏,則通過ClassLoader.getSystemClassLoader()獲取,否則直接使用傳入的ClassLoader
    loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
    acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
    reload();
}

繼續(xù)跟reload()方法沙合,如下所示奠伪。

//重新加載
public void reload() {
    //清空保存加載的實現(xiàn)類的LinkedHashMap
    providers.clear();
    //構(gòu)造延遲加載的迭代器
    lookupIterator = new LazyIterator(service, loader);
}

繼續(xù)跟進懶加載迭代器的構(gòu)造函數(shù),如下所示首懈。

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

可以看到绊率,會將需要加載的接口的Class對象和類加載器賦值給LazyIterator的成員變量。

當我們在程序中迭代獲取對象實例時猜拾,首先在成員變量providers中查找是否有緩存的實例對象即舌。如果存在則直接返回,否則調(diào)用lookupIterator延遲加載迭代器進行加載挎袜。

迭代器進行邏輯判斷的代碼如下所示:

//迭代ServiceLoader的方法
public Iterator<S> iterator() {
    return new Iterator<S>() {
        //獲取保存實現(xiàn)類的LinkedHashMap<String,S>的迭代器
        Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator();
        //判斷是否有下一個元素
        public boolean hasNext() {
            //如果knownProviders存在元素顽聂,則直接返回true
            if (knownProviders.hasNext())
                return true;
            //返回延遲加載器是否存在元素
            return lookupIterator.hasNext();
        }
        //獲取下一個元素
        public S next() {
            //如果knownProviders存在元素,則直接獲取
            if (knownProviders.hasNext())
                return knownProviders.next().getValue();
            //獲取延遲迭代器lookupIterator中的元素
            return lookupIterator.next();
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }
    };
}

LazyIterator加載類的流程如下代碼所示

//判斷是否擁有下一個實例
private boolean hasNextService() {
    //如果擁有下一個實例盯仪,直接返回true
    if (nextName != null) {
        return true;
    }
    //如果實現(xiàn)類的全名為null
    if (configs == null) {
        try {
            //獲取全文件名紊搪,文件相對路徑+文件名稱(包名+接口名)
            String fullName = PREFIX + service.getName();
            //類加載器為空,則通過ClassLoader.getSystemResources()方法獲取
            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()) {
        //如果configs中沒有更過的元素耀石,則直接返回false
        if (!configs.hasMoreElements()) {
            return false;
        }
        //解析包結(jié)構(gòu)
        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 {
        //通過c.newInstance()生成對象實例
        S p = service.cast(c.newInstance());
        //將生成的對象實例保存到緩存中(LinkedHashMap<String,S>)
        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() {
    if (acc == null) {
        return hasNextService();
    } else {
        PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
            public Boolean run() { return hasNextService(); }
        };
        return AccessController.doPrivileged(action, acc);
    }
}

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

最后,給出整個java.util.ServiceLoader的類爸黄,如下所示:

package java.util;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;


public final class ServiceLoader<S>  implements Iterable<S> {
    // 加載具體實現(xiàn)類信息的前綴滞伟,也就是以接口命名的文件需要放到Jar包中的META-INF/services/目錄下
    private static final String PREFIX = "META-INF/services/";

    // 需要加載的接口
    private final Class<S> service;
    
    // 類加載器,用于加載以接口命名的文件中配置的接口的實現(xiàn)類
    private final ClassLoader loader;
    
    // 創(chuàng)建ServiceLoader時采用的訪問控制上下文環(huán)境
    private final AccessControlContext acc;
    
    // 用來緩存已經(jīng)加載的接口實現(xiàn)類炕贵,其中梆奈,Key是接口實現(xiàn)類的完整類名,Value為實現(xiàn)類對象
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
    
    // 用于延遲加載實現(xiàn)類的迭代器
    private LazyIterator lookupIterator;
    
    //重新加載
    public void reload() {
        //清空保存加載的實現(xiàn)類的LinkedHashMap
        providers.clear();
        //構(gòu)造延遲加載的迭代器
        lookupIterator = new LazyIterator(service, loader);
    }
    
    //構(gòu)造ServiceLoader對象
    private ServiceLoader(Class<S> svc, ClassLoader cl) {
        //如果傳入的Class對象為空称开,則判處空指針異常
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        //如果傳入的ClassLoader為空亩钟,則通過ClassLoader.getSystemClassLoader()獲取,否則直接使用傳入的ClassLoader
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }
    
    private static void fail(Class<?> service, String msg, Throwable cause)
        throws ServiceConfigurationError
    {
        throw new ServiceConfigurationError(service.getName() + ": " + msg,
                                            cause);
    }
    
    private static void fail(Class<?> service, String msg)
        throws ServiceConfigurationError
    {
        throw new ServiceConfigurationError(service.getName() + ": " + msg);
    }
    
    private static void fail(Class<?> service, URL u, int line, String msg)
        throws ServiceConfigurationError
    {
        fail(service, u + ":" + line + ": " + msg);
    }
    
    // Parse a single line from the given configuration file, adding the name
    // on the line to the names list.
    //
    private int parseLine(Class<?> service, URL u, BufferedReader r, int lc,
                          List<String> names)
        throws IOException, ServiceConfigurationError
    {
        String ln = r.readLine();
        if (ln == null) {
            return -1;
        }
        int ci = ln.indexOf('#');
        if (ci >= 0) ln = ln.substring(0, ci);
        ln = ln.trim();
        int n = ln.length();
        if (n != 0) {
            if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
                fail(service, u, lc, "Illegal configuration-file syntax");
            int cp = ln.codePointAt(0);
            if (!Character.isJavaIdentifierStart(cp))
                fail(service, u, lc, "Illegal provider-class name: " + ln);
            for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
                cp = ln.codePointAt(i);
                if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
                    fail(service, u, lc, "Illegal provider-class name: " + ln);
            }
            if (!providers.containsKey(ln) && !names.contains(ln))
                names.add(ln);
        }
        return lc + 1;
    }
    
    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();
    }
    
    // Private inner class implementing fully-lazy provider lookupload
    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() {
            //如果擁有下一個實例鳖轰,直接返回true
            if (nextName != null) {
                return true;
            }
            //如果實現(xiàn)類的全名為null
            if (configs == null) {
                try {
                    //獲取全文件名清酥,文件相對路徑+文件名稱(包名+接口名)
                    String fullName = PREFIX + service.getName();
                    //類加載器為空,則通過ClassLoader.getSystemResources()方法獲取
                    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()) {
                //如果configs中沒有更過的元素焰轻,則直接返回false
                if (!configs.hasMoreElements()) {
                    return false;
                }
                //解析包結(jié)構(gòu)
                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 {
                //通過c.newInstance()生成對象實例
                S p = service.cast(c.newInstance());
                //將生成的對象實例保存到緩存中(LinkedHashMap<String,S>)
                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() {
            if (acc == null) {
                return hasNextService();
            } else {
                PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                    public Boolean run() { return hasNextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }
    
        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);
            }
        }
    
        public void remove() {
            throw new UnsupportedOperationException();
        }
    
    }
    
    //迭代ServiceLoader的方法
    public Iterator<S> iterator() {
        return new Iterator<S>() {
            //獲取保存實現(xiàn)類的LinkedHashMap<String,S>的迭代器
            Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator();
            //判斷是否有下一個元素
            public boolean hasNext() {
                //如果knownProviders存在元素,則直接返回true
                if (knownProviders.hasNext())
                    return true;
                //返回延遲加載器是否存在元素
                return lookupIterator.hasNext();
            }
            //獲取下一個元素
            public S next() {
                //如果knownProviders存在元素昆雀,則直接獲取
                if (knownProviders.hasNext())
                    return knownProviders.next().getValue();
                //獲取延遲迭代器lookupIterator中的元素
                return lookupIterator.next();
            }
    
            public void remove() {
                throw new UnsupportedOperationException();
            }
    
        };
    }
    
    //通過ClassLoader加載指定類的Class鹦马,并將返回結(jié)果封裝到ServiceLoader對象中
    public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
    {
        return new ServiceLoader<>(service, loader);
    }
    
    //根據(jù)類的Class對象加載指定的類胧谈,返回ServiceLoader對象
    public static <S> ServiceLoader<S> load(Class<S> service) {
        //獲取當前線程的類加載器
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        //動態(tài)加載指定的類,將類加載到ServiceLoader中
        return ServiceLoader.load(service, cl);
    }
    
    public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {
        ClassLoader cl = ClassLoader.getSystemClassLoader();
        ClassLoader prev = null;
        while (cl != null) {
            prev = cl;
            cl = cl.getParent();
        }
        return ServiceLoader.load(service, prev);
    }
    
    /**
     * Returns a string describing this service.
     *
     * @return  A descriptive string
     */
    public String toString() {
        return "java.util.ServiceLoader[" + service.getName() + "]";
    }
}

SPI總結(jié)

最后荸频,對Java提供的SPI機制進行簡單的總結(jié)。

優(yōu)點:

能夠?qū)崿F(xiàn)項目解耦客冈,使得第三方服務(wù)模塊的裝配控制的邏輯與調(diào)用者的業(yè)務(wù)代碼分離旭从,而不是耦合在一起。應(yīng)用程序可以根據(jù)實際業(yè)務(wù)情況啟用框架擴展或替換框架組件场仲。

缺點:

  • 多個并發(fā)多線程使用ServiceLoader類的實例是不安全的
  • 雖然ServiceLoader也算是使用的延遲加載和悦,但是基本只能通過遍歷全部獲取,也就是接口的實現(xiàn)類全部加載并實例化一遍渠缕。

參考:深入理解Java中的spi機制

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鸽素,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子亦鳞,更是在濱河造成了極大的恐慌馍忽,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件燕差,死亡現(xiàn)場離奇詭異遭笋,居然都是意外死亡,警方通過查閱死者的電腦和手機徒探,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門瓦呼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人测暗,你說我怎么就攤上這事央串。” “怎么了碗啄?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵质和,是天一觀的道長。 經(jīng)常有香客問我挫掏,道長侦另,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任尉共,我火速辦了婚禮褒傅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘袄友。我一直安慰自己殿托,他們只是感情好,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布剧蚣。 她就那樣靜靜地躺著支竹,像睡著了一般旋廷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上礼搁,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天饶碘,我揣著相機與錄音,去河邊找鬼馒吴。 笑死扎运,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的饮戳。 我是一名探鬼主播豪治,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼扯罐!你這毒婦竟也來了负拟?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤歹河,失蹤者是張志新(化名)和其女友劉穎掩浙,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體启泣,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡涣脚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了寥茫。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片遣蚀。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖纱耻,靈堂內(nèi)的尸體忽然破棺而出芭梯,到底是詐尸還是另有隱情,我是刑警寧澤弄喘,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布玖喘,位于F島的核電站,受9級特大地震影響蘑志,放射性物質(zhì)發(fā)生泄漏累奈。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一急但、第九天 我趴在偏房一處隱蔽的房頂上張望澎媒。 院中可真熱鬧,春花似錦波桩、人聲如沸戒努。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽储玫。三九已至侍筛,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間撒穷,已是汗流浹背匣椰。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留桥滨,地道東北人窝爪。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像齐媒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子纷跛,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容