Springboot中的SPI 機(jī)制

SPI的全稱(chēng)是Service Provider Interface, 直譯過(guò)來(lái)就是"服務(wù)提供接口", 聽(tīng)起來(lái)挺別扭的, 所以我試著去理解了一下, 就將它翻譯為"服務(wù)提供商接口"吧.

我們都知道, 一個(gè)接口是可以有很多種實(shí)現(xiàn)的. 例如搜索,可以是搜索系統(tǒng)的硬盤(pán),也可以是搜索數(shù)據(jù)庫(kù).系統(tǒng)的設(shè)計(jì)者為了降低耦合,并不想在硬編碼里面寫(xiě)死具體的搜索方式,而是希望由服務(wù)提供者來(lái)選擇使用哪種搜索方式, 這個(gè)時(shí)候就可以選擇使用SPI機(jī)制.

JDK中的SPI

讓我們通過(guò)一個(gè)非常簡(jiǎn)單的例子,來(lái)認(rèn)識(shí)一下java里面的SPI機(jī)制.

  1. 定義一個(gè)搜索接口Search
package com.north.spilat.service; 

import java.util.List; 
public interface Search {
     List<String> search(String keyword);
 }
  1. 實(shí)現(xiàn)接口從數(shù)據(jù)庫(kù)查詢(xún)
package com.north.spilat.service.impl; 

import com.north.spilat.service.Search; 
import java.util.List; 

/** * @author laihaohua */
public class DatabaseSearch implements Search {

        @Override public List<String> search(String keyword) {
            System.out.println("now use database search. keyword:" + keyword); return null;
        }

    }
  1. 實(shí)現(xiàn)接口從文件系統(tǒng)查詢(xún)
package com.north.spilat.service.impl; 

import com.north.spilat.service.Search; 
import java.util.List; 
/** * @author laihaohua */
public class FileSearch implements Search {

     @Override public List<String> search(String keyword) {
         System.out.println("now use file system search. keyword:" + keyword); return null;
     }

 }
    1. src\main\resources 創(chuàng)建一個(gè)目錄 META-INF\services\com.north.spilat.service.Search
    2. 然后 在com.north.spilat.service.Search下面創(chuàng)建兩個(gè)文件,以上面接口的具體實(shí)現(xiàn)類(lèi)的全限定名稱(chēng)為文件名,即:
      com.north.spilat.service.impl.DatabaseSearch
      com.north.spilat.service.impl.FileSearch
      整個(gè)工程目錄如下:
      image
  1. 新建一個(gè)main方法測(cè)試一下

package com.north.spilat.main; 

import com.north.spilat.service.Search; 
import java.util.Iterator; 
import java.util.ServiceLoader; 
public class Main { public static void main(String[] args) {
         System.out.println("Hello World!");
         ServiceLoader<Search> s = ServiceLoader.load(Search.class);
         Iterator<Search> searchList = s.iterator(); while (searchList.hasNext()) {
             Search curSearch = searchList.next();
             curSearch.search("test");
         }
     }
 }

運(yùn)行一下,輸出如下:

Hello World!now use database search. keyword:testnow use file system search. keyword:test

如你所見(jiàn), SPI機(jī)制已經(jīng)定義好了加載服務(wù)的流程框架, 你只需要按照約定, 在META-INF/services目錄下面, 以接口的全限定名稱(chēng)為名創(chuàng)建一個(gè)文件夾(com.north.spilat.service.Search), 文件夾下再放具體的實(shí)現(xiàn)類(lèi)的全限定名稱(chēng)(com.north.spilat.service.impl.DatabaseSearch), 系統(tǒng)就能根據(jù)這些文件,加載不同的實(shí)現(xiàn)類(lèi).

再回到上面的main方法,其實(shí)沒(méi)有什么特別的,除了一句
ServiceLoader.load(Search.class);
ServiceLoader.class是一個(gè)工具類(lèi),根據(jù)META-INF/services/xxxInterfaceName下面的文件名,加載具體的實(shí)現(xiàn)類(lèi).

從load(Search.class)進(jìn)去,我們來(lái)扒一下這個(gè)類(lèi)

  1. 可以看到,里面并沒(méi)有很多邏輯,主要邏輯都交給了LazyIterator這類(lèi)
/* *入口, 獲取一下當(dāng)前類(lèi)的類(lèi)加載器,然后調(diào)用下一個(gè)靜態(tài)方法 */
 public static <S> ServiceLoader<S> load(Class<S> service) {
     ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl);

}

/* *這個(gè)也沒(méi)有什么邏輯,直接調(diào)用構(gòu)造方法 */
 public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader)
 { 
    return new ServiceLoader<>(service, loader);
 } 
/** * 也沒(méi)有什么邏輯,直接調(diào)用reload */
 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();
 } 
/** * 直接實(shí)例化一個(gè)懶加載的迭代器 */
 public void reload() {
     providers.clear();
     lookupIterator = new LazyIterator(service, loader);
 }
  1. LazyIterator這個(gè)迭代器只需要關(guān)心hasNext()和next(), hasNext()里面又只是單純地調(diào)用hasNextService(). 不用說(shuō), next()里面肯定也只是單純地調(diào)用了nextService();
 private boolean hasNextService() { 
    if (nextName != null) { 
        // nextName不為空,說(shuō)明加載過(guò)了,而且服務(wù)不為空 
         return true;
     } 
    // configs就是所有的實(shí)現(xiàn)類(lèi)文件名字
     if (configs == null) { 
        try { 
              // PREFIX是 /META-INF/services 
              // service.getName() 是接口的全限定名稱(chēng)
             String fullName = PREFIX + service.getName(); 
              // loader == null, 說(shuō)明是bootstrap類(lèi)加載器
             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()) { 
                // 該目錄下什么文件都沒(méi)有
                 return false;
             } 
            //就是判斷一下configs.nextElement()的格式是不是對(duì)的
             pending = parse(service, configs.nextElement());
     }
     nextName = pending.next(); 
     return true;
 }
  1. 再來(lái)看看nextService干了啥
private S nextService() { 
    // 校驗(yàn)一下
     if (!hasNextService()) throw new NoSuchElementException();
     String cn = nextName;
     nextName = null;
     Class<?> c = null; 
      try { 
          // 嘗試一下是否能加載該類(lèi)
         c = Class.forName(cn, false, loader);
     } catch (ClassNotFoundException x) {
         fail(service,"Provider " + cn + " not found");
     } 
      // 是不是service的子類(lèi),或者同一個(gè)類(lèi)
     if (!service.isAssignableFrom(c)) {
         fail(service,"Provider " + cn  + " not a subtype");
     } 
    try { 
          // 實(shí)例化這個(gè)類(lèi), 然后向上轉(zhuǎn)一下
         S p = service.cast(c.newInstance()); 
          // 緩存起來(lái),避免重復(fù)加載
         providers.put(cn, p); 
         return p;
     } catch (Throwable x) {
         fail(service,"Provider " + cn + " could not be instantiated",x);
     } throw new Error();          
     // This cannot happen
 }

從上面的代碼就可以看出來(lái), 所謂的懶加載,就是等到調(diào)用hasNext()再查找服務(wù), 調(diào)用next()才實(shí)例化服務(wù)類(lèi).

JDK的SPI大概就是這么一個(gè)邏輯了, 服務(wù)提供商安裝約定,將具體的實(shí)現(xiàn)類(lèi)名稱(chēng)放到/META-INF/services/xxx下, ServiceLoader就可以根據(jù)服務(wù)提供者的意愿, 加載不同的實(shí)現(xiàn)了, 避免硬編碼寫(xiě)死邏輯, 從而達(dá)到解耦的目的.

當(dāng)然, 從上面這個(gè)簡(jiǎn)單的例子可能大家會(huì)看不出來(lái),SPI是如何達(dá)到解耦的效果的. 所以下面, 我們一起來(lái)看看,開(kāi)源框架中是怎么利用SPI機(jī)制來(lái)解耦的. 體會(huì)一下SPI的魅力.

springboot 中的SPI

以前還在實(shí)習(xí)的時(shí)候,老大就跟我說(shuō)過(guò)一段話(huà),他說(shuō)你沒(méi)事可以多點(diǎn)研究開(kāi)源框架,因?yàn)檫@些開(kāi)源代碼每天都不知道被人擼幾遍,所以他們的代碼從設(shè)計(jì)到實(shí)現(xiàn),都是非常優(yōu)秀的,可以從中學(xué)到不少東西.

而spring框架這些年來(lái),基本上可以說(shuō)是開(kāi)源界扛把子,江湖上無(wú)人不知無(wú)人不曉.其源碼的設(shè)計(jì)也是出了名的優(yōu)雅,超高拓展性超低耦合性.

那它是怎么解耦的呢? 拓展點(diǎn)機(jī)制便是其中法寶之一(注意用詞,是之一 哈-_-)

從神奇的starter說(shuō)起

剛剛接觸springboot的時(shí)候, 真的覺(jué)得各種spring-xx-starter和xx-spring-starter非常的神奇. 為什么在pom文件添加一個(gè)依賴(lài)就能引入一個(gè)復(fù)雜的插件了呢? 帶著這個(gè)疑問(wèn),我開(kāi)始了我的走進(jìn)科學(xué)之旅.

dubbo框架聽(tīng)說(shuō)在國(guó)內(nèi)用的公司挺多的,所以這里, 我們就以dubbo-spring-boot-starter為例,來(lái)看看springboot中是如何高效解耦的.

回想一下, 如果我們要在springboot工程里面引入dubbo模塊, 需要怎么做.

  1. 在pom文件引入dubbo-spring-boot-starter的依賴(lài).
<dependency>
        <groupId>com.alibaba.spring.boot</groupId>
        <artifactId>dubbo-spring-boot-starter</artifactId>
        <version>2.0.0</version>
 </dependency></pre>
  1. 在application.properties文件配置好dubbo相關(guān)參數(shù)
    image.png
spring.dubbo.server=true spring.dubbo.application.name=north-spilat-server

#
spring.dubbo.registry.id=defaultRegistry
#
spring.dubbo.registry.address=127.0.0.1 #
spring.dubbo.registry.port=2181 #
spring.dubbo.registry.protocol=zookeeper
#
spring.dubbo.protocol.name=dubbo
#
spring.dubbo.protocol.port=20881 #
spring.dubbo.module.name=north-spilat-server
#
spring.dubbo.consumer.check=false #
spring.dubbo.provider.timeout=3000 #
spring.dubbo.consumer.retries=0 #
spring.dubbo.consumer.timeout=3000
  1. 在spring-boot的啟動(dòng)類(lèi)加上對(duì)應(yīng)的注解
package com.north.spilat.main; 

import com.alibaba.dubbo.spring.boot.annotation.EnableDubboConfiguration; 
import org.springframework.boot.SpringApplication; 
import org.springframework.boot.autoconfigure.SpringBootApplication; 
import org.springframework.context.annotation.ComponentScan; 
/** * @author laihaohua */ 
@SpringBootApplication
@ComponentScan(basePackages = {"com.north.*"})
@EnableDubboConfiguration public class SpringBootMain { public static void main(String[] args) {
        SpringApplication.run(SpringBootMain.class, args);
    }
}
  1. 定義接口, 實(shí)現(xiàn)并調(diào)用

接口

package com.north.spilat.service; 
/** * @author laihaohua */
public interface DubboDemoService {
    String test(String params);
}

實(shí)現(xiàn)接口

package com.north.spilat.service.impl; 
import com.alibaba.dubbo.config.annotation.Service; 
import com.north.spilat.service.DubboDemoService; 
import org.springframework.stereotype.Repository; 
/** * @author laihaohua */ 
@Service
@Repository("dubboDemoService") 
public class DubboDemoServiceImpl implements DubboDemoService {
    @Override 
    public String test(String params) { 
        return System.currentTimeMillis() + "-" + params ;
    }
}

寫(xiě)個(gè)controller調(diào)用dubbo接口

package com.north.spilat.controller; 
import com.north.spilat.service.DubboDemoService; 
import org.springframework.web.bind.annotation.RequestMapping; 
import org.springframework.web.bind.annotation.RestController; 
import javax.annotation.Resource; 
/** * @author laihaohua */ 
@RestController public class HelloWorldController {
    @Resource 
    private DubboDemoService dubboDemoService;

    @RequestMapping("/saveTheWorld") 
    public String index(String name) { 
          return dubboDemoService.test(name);
    }
}

做完以上4步(zookeeper等環(huán)境自己裝一下)后, 啟動(dòng)SpringBootMain類(lèi), 一個(gè)帶有dubbo模塊的springboot工程就這樣搭好了, 真的就這么簡(jiǎn)單.

但是事情越簡(jiǎn)單,背后就越不簡(jiǎn)單.一定是有人默默地為我們做了很多事. 這個(gè)人就是"dubbo-spring-boot-starter"

dubbo-spring-boot-starter的奧秘

image

上圖是dubbo-spring-boot-starter.jar包的結(jié)構(gòu). 內(nèi)容還真不少, 但是聰明的你肯定想到了, 既然我們上一節(jié)說(shuō)到了SPI是跟META-INF息息相關(guān)的,那我們這一節(jié)也必然是這樣. 因此, 這里我們先看一下META-INF目錄下面有什么.

dubbo/com.alibaba.dubbo.rpc.InvokerListener

dubbosubscribe=com.alibaba.dubbo.spring.boot.listener.ConsumerSubscribeListener

這個(gè)目錄下的文件只有一行,看著和上面的jdk的SPI真的是像.沒(méi)錯(cuò), 這的確是一種拓展點(diǎn), 是dubbo里面的一種拓展點(diǎn)約定, 但是我們這里也不深入, 有機(jī)會(huì)可以另開(kāi)一篇討論一下(這題超綱了)

  1. spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.dubbo.spring.boot.DubboAutoConfiguration,\
com.alibaba.dubbo.spring.boot.DubboProviderAutoConfiguration,\
com.alibaba.dubbo.spring.boot.DubboConsumerAutoConfiguration

org.springframework.context.ApplicationListener=\
com.alibaba.dubbo.spring.boot.context.event.DubboBannerApplicationListener

哇哇哇,文件就是以spring命名,文件內(nèi)容還涉及到這么多spring類(lèi). 確認(rèn)過(guò)眼神, 我遇上對(duì)的...文件. 但是別急, 下面還有一個(gè)spring.providers文件

  1. spring.providers
provides: dubbo-spring-boot-starter

spring.providers就這么簡(jiǎn)單的一句, 有點(diǎn)失望了.所以我們還是來(lái)關(guān)注一下spring.factories吧.

image

在IDEA里面搜一下spring.factories這個(gè)文件. 不搜不知道, 一搜嚇一跳. 原來(lái)基本上每一個(gè)springboot相關(guān)的jar包里面都會(huì)有一個(gè)這樣的文件.

物理學(xué)家在做實(shí)驗(yàn)之前, 總是喜歡推理一番, 得到一個(gè)預(yù)測(cè)的結(jié)論, 然后再通過(guò)實(shí)驗(yàn)結(jié)果來(lái)證實(shí)或推翻預(yù)測(cè)的結(jié)論.

因此, 基于JDK里面的SPI機(jī)制, 在這里我們也可以做一個(gè)大膽的預(yù)測(cè):spring框架里面一定是有一個(gè)類(lèi)似于ServiceLoader的類(lèi), 專(zhuān)門(mén)從META-INF/spring.factories里面的配置,加載特定接口的實(shí)現(xiàn).

結(jié)果不用說(shuō), 這個(gè)預(yù)測(cè)肯定是準(zhǔn)確, 不然我上面這么多字不就白寫(xiě)啦. 但是怎么證明我們的預(yù)測(cè)是準(zhǔn)確的呢. 讓我們也來(lái)做一次"實(shí)驗(yàn)".

springboot的啟動(dòng)過(guò)程

要弄清楚springboot的啟動(dòng)過(guò)程, 最好的辦法就研讀它的源碼了.

而springboot的代碼還是非常"人性化"的, 不像其他開(kāi)源框架,不知道從何看起.springboot明明確確地告訴你了, 它的入口就是main方法.因此, 讀springboot的代碼, 還算是比較愜意的.

image

上圖就是一個(gè)springboot工程的啟動(dòng)過(guò)程.首先是連續(xù)兩個(gè)重載的靜態(tài)run方法, 靜態(tài)run方法內(nèi)部會(huì)調(diào)用構(gòu)造方法實(shí)例化SpringApplication對(duì)象, 構(gòu)造方法內(nèi)部是調(diào)用initialiaze()進(jìn)行初始化的,實(shí)例化,再調(diào)用一個(gè)成員方法run()來(lái)正式啟動(dòng). 可見(jiàn), 整個(gè)啟動(dòng)過(guò)程主要的邏輯都在initialiaze方法和成員run方法內(nèi)部了.

看一下initialiaze()的邏輯

@SuppressWarnings({ "unchecked", "rawtypes" }) 
private void initialize(Object[] sources) { 
    // sources一般是Configuration類(lèi)或main方法所在類(lèi) // 可以有多個(gè)
    if (sources != null && sources.length > 0) { 
        this.sources.addAll(Arrays.asList(sources));
    } 
// 判斷是否是web環(huán)境 
// classLoader能加載到 
// "javax.servlet.Servlet", 
// "org.springframework.web.context.ConfigurableWebApplicationContext" 
// 這兩個(gè)類(lèi)就是web環(huán)境 
    this.webEnvironment = deduceWebEnvironment(); 
// 加載initializers 和listeners // getSpringFactoriesInstances顧名思義, 
// 就是加載某個(gè)接口的工廠(chǎng)實(shí)例, 
// 看起來(lái)像是我們要找的"ServiceLoader"了
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); 
// 找到main方法所在的類(lèi)
    this.mainApplicationClass = deduceMainApplicationClass();
}

運(yùn)氣還算不錯(cuò),"嫌疑犯"getSpringFactoriesInstances就露出水面了, 來(lái)看看它的邏輯

/** * 參數(shù)type就是要加載的接口的class */
    private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) { // 直接調(diào)用重載方法
        return getSpringFactoriesInstances(type, new Class<?>[] {});
    } 
  private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,Class<?>[] parameterTypes, Object... args) { 
        // 獲取當(dāng)前線(xiàn)程的classLoader 
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 
        // Use names and ensure unique to protect against duplicates 
        // 翻譯一下原文注釋就是用names來(lái)去重 
        // 注意這里, 我們尋找的"ServiceLoader"終于出現(xiàn)了 
        // 就是SpringFactoriesLoader
        Set<String> names = new LinkedHashSet<String>(
                SpringFactoriesLoader.loadFactoryNames(type, classLoader)); // 是用java反射來(lái)實(shí)例化 
        List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
                classLoader, args, names); 
        // 根據(jù)@Order注解來(lái)排一下序 
       AnnotationAwareOrderComparator.sort(instances); 
        // 返回這個(gè)接口的所有實(shí)現(xiàn)實(shí)例
        return instances;
    }

然后很快就找到了我們想找的SpringFactoriesLoader, 而且這個(gè)類(lèi)非常小, 代碼比JDK的ServiceLoader還少. 那我們仔細(xì)看一下他里面都有啥.

  1. FACTORIES_RESOURCE_LOCATION 正是指向我們上面所說(shuō)的META-INF/spring.factories
  2. loadFactories, 從META-INF/spring.factories查找指定的接口實(shí)現(xiàn)類(lèi)并實(shí)例化, 其中查找是通過(guò)調(diào)用loadFactoryNames
  3. loadFactoryNames從指定的位置查找特定接口的實(shí)現(xiàn)類(lèi)的全限定名稱(chēng)
  4. instantiateFactory 實(shí)例化

這個(gè)類(lèi)就是springboot里面的"ServiceLoader",它提供了一種機(jī)制,可以讓服務(wù)提供商指定某種接口的實(shí)現(xiàn)(可以是多個(gè)),例如上面的ApplicationContextInitializer.class和ApplicationListener.class接口, 如果我們想在我們的模塊里面指定我們的實(shí)現(xiàn),或者想在現(xiàn)有的代碼上加上我們的某個(gè)實(shí)現(xiàn),就可以在/META-INF/spring.factories里面指定. 等一下下面我會(huì)寫(xiě)一個(gè)具體的例子, 可以讓大家更好的理解一下.

/** * 省略import**/
public abstract class SpringFactoriesLoader { 
    private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class); 
  /** * The location to look for factories.
     *  查找工廠(chǎng)實(shí)現(xiàn)類(lèi)的位置
     * <p>Can be present in multiple JAR files.
     *   可以在多個(gè)jar包中
     * 這不就是我們一直在尋找的META-INF/spring.factories嘛
     * 終于找到了 */
    public static final String FACTORIES_RESOURCE_LOCATION ="META-INF/spring.factories";
 
    /** * 查找    并實(shí)例化指定的工廠(chǎng)類(lèi)實(shí)現(xiàn) */
    public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) {
        Assert.notNull(factoryClass, "'factoryClass' 
        must not be null");
        ClassLoader classLoaderToUse = classLoader; if (classLoaderToUse == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
      } 

      // 最終是調(diào)用loadFactoryNames
        List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse); 
        if (logger.isTraceEnabled()) {
            logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
        }
        List<T> result = new ArrayList<T>(factoryNames.size()); for (String factoryName : factoryNames) { 
        // 一個(gè)個(gè)的實(shí)例化
         result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
        } 
    // 排序
     AnnotationAwareOrderComparator.sort(result); 
    return result;
    }

/** * 從META-INF/spring.factories查找指定接口的實(shí)現(xiàn)類(lèi)的

     * 全限定類(lèi)名稱(chēng) */
    public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) { 
        // 接口的類(lèi)名稱(chēng)
        String factoryClassName = factoryClass.getName(); 
        try { 
            //從META-INF/spring.factories加載文件資源
            Enumeration<URL> urls = (classLoader != null ?classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
            ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            List<String> result = new ArrayList<String>(); 
            while (urls.hasMoreElements()) { 
                // 一個(gè)url代表一個(gè)spring.factories文件
                URL url = urls.nextElement(); 
                // 加載所有的屬性, 一般是 xxx接口=impl1,impl2 這種形式的
                Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url)); 
                // 根據(jù)接口名獲取的類(lèi)似"impl1,impl2"的字符串
                String factoryClassNames = properties.getProperty(factoryClassName) 
                // 以逗號(hào)分隔,轉(zhuǎn)化成列表
                 result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
            } 
            // 返回實(shí)現(xiàn)類(lèi)名的列表
            return result;
        } catch (IOException ex) { 
            throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
                    "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    } 
    /** * 根據(jù)類(lèi)名的全限定名稱(chēng)實(shí)例化 */ 
    @SuppressWarnings("unchecked") 
    private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) { 
        try { 
            // 查找類(lèi)
            Class<?> instanceClass = ClassUtils.forName(instanceClassName, classLoader); 
            // 校驗(yàn)是不是該接口類(lèi)或該接口類(lèi)的實(shí)現(xiàn)類(lèi)
            if (!factoryClass.isAssignableFrom(instanceClass)) { 
                  throw new IllegalArgumentException( "Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]");
            }
            Constructor<?> constructor = instanceClass.getDeclaredConstructor();
            ReflectionUtils.makeAccessible(constructor); // 反射實(shí)例化
            return (T) constructor.newInstance();
        } catch (
            Throwable ex) { throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), ex);
        }
    }

}

看完SpringFactoriesLoader這個(gè)類(lèi), initialize()方法的邏輯也就看完了. 接著再看另外一個(gè)重要方法run(String... args)

/** * Run the Spring application, creating and refreshing a new
     * {@link ApplicationContext}.
     * @param args the application arguments (usually passed from a Java main method)
     * @return a running {@link ApplicationContext} */
    public ConfigurableApplicationContext run(String... args) { 
        // 用于監(jiān)測(cè)啟動(dòng)時(shí)長(zhǎng)等等
        StopWatch stopWatch = new StopWatch();
        stopWatch.start(); // springboot的上下文
        ConfigurableApplicationContext context = null;
        FailureAnalyzers analyzers = null; 
        // 配置headless模式
         configureHeadlessProperty(); 
        // 啟動(dòng)監(jiān)聽(tīng)器, 可以配置到spring.factories中去
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
         try { 
            // 封裝參數(shù)
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args); 
            // 配置environment 
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments); 
            // 打印banner 
            Banner printedBanner = printBanner(environment); 
            // 創(chuàng)建上下文
            context = createApplicationContext();
            analyzers = new FailureAnalyzers(context); 
            // 先初始化上下文
             prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner); 
             // spring 經(jīng)典的refresh()過(guò)程, 大部分的邏輯都在里面 
            // 這里不再深入, 讀者可以自行研讀代碼或搜索引擎
            refreshContext(context);
            afterRefresh(context, applicationArguments);
            listeners.finished(context, null);
            stopWatch.stop(); 
            if (this.logStartupInfo) { 
                  new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            } return context;
        } catch (Throwable ex) {
            handleRunFailure(context, listeners, analyzers, ex); throw new IllegalStateException(ex);
        }
    }

這個(gè)方法就是springboot啟動(dòng)的主要邏輯了,內(nèi)容很多,如果要全部說(shuō)清楚的話(huà), 恐怕再寫(xiě)幾遍博客也說(shuō)不完(給人家springboot一點(diǎn)最起碼的尊重好不好, 想一篇文章就理解透徹人家整個(gè)框架,人家不要面子的呀).所以這里就不會(huì)再深入,對(duì)于本文,只要知道這個(gè)run()方法是啟動(dòng)的主要邏輯就可以了, 另外記住

context = createApplicationContext();
refreshContext(context);

這兩行代碼,等下我們還會(huì)看到它的.

dubbo-spring-boot-starter的原理

上面說(shuō)了很多, 但是為什么springboot引入一個(gè)starter的依賴(lài),就能引入一個(gè)復(fù)雜的模塊內(nèi). 這里通過(guò)dubbo-spring-boot-starter來(lái)研究一下.

我們回顧一下dubbo-spring-boot-starter里面spring.factories. 可以發(fā)現(xiàn)里面配置了兩個(gè)接口, 一個(gè)是EnableAutoConfiguration,一個(gè)是ApplicationListener.

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.dubbo.spring.boot.DubboAutoConfiguration,\
com.alibaba.dubbo.spring.boot.DubboProviderAutoConfiguration,\
com.alibaba.dubbo.spring.boot.DubboConsumerAutoConfiguration

org.springframework.context.ApplicationListener=\
com.alibaba.dubbo.spring.boot.context.event.DubboBannerApplicationListener

監(jiān)聽(tīng)器看名稱(chēng)就知道了是用于啟動(dòng)的時(shí)候打印banner, 所以這里暫時(shí)不看, 我們先來(lái)看一下EnableAutoConfiguration是哪里用到的.

從main方法開(kāi)始一路debug,終于在A(yíng)utoConfigurationImportSelector類(lèi)中發(fā)現(xiàn)了一行代碼:

SpringFactoriesLoader.loadFactoryNames( getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()) 

其中g(shù)etSpringFactoriesLoaderFactoryClass()就是返回EnableAutoConfiguration.class

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
        AnnotationAttributes attributes) {
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
            getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
                    + "are using a custom packaging, make sure that file is correct."); 
    return configurations;
} 
/** * Return the class used by {@link SpringFactoriesLoader} to load configuration
 * candidates.
 * @return the factory class */
protected Class<?> getSpringFactoriesLoaderFactoryClass() { 
    return EnableAutoConfiguration.class;
}

如下圖可以發(fā)現(xiàn),EnableAutoConfiguration.class的實(shí)現(xiàn)會(huì)有很多, 只要你在spring.fatories配置了,它都會(huì)給你加載進(jìn)來(lái)

image

加載了之后,又干嘛呢, 往下看,可以發(fā)現(xiàn)大概流程是這樣:

  1. this.reader.loadBeanDefinitions(configClasses); 把這些類(lèi)讀進(jìn)來(lái)準(zhǔn)備解析
  2. registerBeanDefinition注冊(cè)到beanDefinitionNames
  3. spring的refresh()最后有一步是finishBeanFactoryInitialization(beanFactory), 這一步時(shí)初始化所有的單例對(duì)象, 最后會(huì)從beanDefinitionNames讀取所有的BeanDefinition,也包括了上面的所有EnableAutoConfiguration實(shí)現(xiàn), 進(jìn)行實(shí)例化
  4. 實(shí)例化EnableAutoConfiguration的具體實(shí)現(xiàn)的時(shí)候,會(huì)執(zhí)行里面的具體邏輯, 以DubboAutoConfiguration為例, 會(huì)定義DubboServer和DubboHealthIndicator兩個(gè)bean, 就可以把dubbo接入進(jìn)來(lái)了.

實(shí)現(xiàn)一個(gè)spring-boot-starter

清楚了原理之后, 要實(shí)現(xiàn)一個(gè)自己的starter就很簡(jiǎn)單了.

假設(shè)我有一個(gè)組件,非常牛逼,具有拯救世界的能力, 你的系統(tǒng)接入后,也就具有了拯救世界的能力了. 那怎么讓你的spring-boot系統(tǒng)可以快速接入這個(gè)牛逼的組件呢. 我來(lái)實(shí)現(xiàn)一個(gè)starter, 你依賴(lài)我這個(gè)starter就可以了

首先定義一個(gè)拯救世界的接口

package com.north.lat.service; /** * @author laihaohua */
public interface SaveTheWorldService { /** *  拯救世界
  * @param name 留名
  * @return
  */ String saveTheWorld(String name);
}

抽象類(lèi)

package com.north.lat.service; 

import lombok.extern.log4j.Log4j; 
import java.util.Random; 
/** * @author laihaohua */ 
@Log4j 
public abstract  class AbstractSaveTheWorldService implements SaveTheWorldService { 
      private final static Random RANDOM = new Random(); 
      private final static String SUCCESS_MSG = "你不要問(wèn)這是什么, 總之就好厲害."; 
      private final static String FAIL_MSG = "拯救世界是個(gè)高風(fēng)險(xiǎn)行業(yè)";

    @Override public String saveTheWorld(String name) { 
        int randomInt = RANDOM.nextInt(100);
        String msg; if((randomInt +  1) > getDieRate()){
            msg = SUCCESS_MSG +"," + name + "拯救了這個(gè)世界!";
        }else{
            msg = FAIL_MSG + "," + name + ",你失敗了,下輩子再來(lái)吧";
        }
        log.info(msg); 
        return msg;
    } 
    /** * 指定死亡率
     * @return
     */
    public abstract int getDieRate();
}

以英雄角色去拯救世界

package com.north.lat.service.impl; import com.north.lat.service.AbstractSaveTheWorldService; 
/** * 英雄拯救世界
 * @author laihaohua */
public class HeroSaveTheWorldImpl extends AbstractSaveTheWorldService { 
      private final static int DIE_RATE = 1;

      @Override 
      public int getDieRate() { 
          return DIE_RATE;
      }
}

普通人去拯救世界

package com.north.lat.service.impl; 
import com.north.lat.service.AbstractSaveTheWorldService; 
/** * 普通人拯救世界
 * @author laihaohua */
public class CommonSaveTheWorldServiceImpl extends AbstractSaveTheWorldService { 
      private final static int DIE_RATE = 99;

      @Override 
      public int getDieRate() { 
        return DIE_RATE;
      }
}

好, 我們這個(gè)超級(jí)牛逼的組件就誕生了, 下面為接入springboot準(zhǔn)備一下, 實(shí)現(xiàn)一個(gè)NbAutoConfiguration如下:

package com.north.lat; 

import com.north.lat.service.SaveTheWorldService; 
import org.springframework.beans.BeansException; 
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 
import org.springframework.beans.factory.support.BeanDefinitionRegistry; 
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; 
import org.springframework.beans.factory.support.GenericBeanDefinition; 
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 
import org.springframework.context.ApplicationContext; 
import org.springframework.context.ApplicationContextAware; 
import org.springframework.context.EnvironmentAware; 
import org.springframework.context.annotation.Configuration; 
import org.springframework.core.env.Environment; 
import org.springframework.core.io.support.SpringFactoriesLoader; 
import java.util.List; 
/** 
 * @author laihaohua
 * 注入environment和applicationContext 以便做一些后續(xù)操作 
*/ 
@Configuration
@ConditionalOnClass(SaveTheWorldService.class) 
public class NbAutoConfiguration implements EnvironmentAware,ApplicationContextAware,BeanDefinitionRegistryPostProcessor { 
      private Environment environment; 

      private ApplicationContext applicationContext;

      @Override 
      public void setEnvironment(Environment environment) { 
          this.environment = environment;
      }

      @Override 
       public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {  
             this.applicationContext = applicationContext;
       }

      @Override 
      public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { 
        // 我這里是從spring.factories加載了SaveTheWorldService的所有實(shí)現(xiàn),
        List<SaveTheWorldService> saveTheWorldServices = SpringFactoriesLoader.loadFactories(SaveTheWorldService.class, this.getClass().getClassLoader()); 
        // 然后用BeanDefinitionRegistry 注冊(cè)到BeanDefinitions
        saveTheWorldServices.forEach(saveTheWorldService->{
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClass(saveTheWorldService.getClass());
            beanDefinition.setLazyInit(false);
            beanDefinition.setAbstract(false);
            beanDefinition.setAutowireCandidate(true);
            beanDefinition.setScope("singleton");
            registry.registerBeanDefinition(saveTheWorldService.getClass().getSimpleName(), beanDefinition);
        });
    }

    @Override 
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }
}

再配置一下spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.north.lat.NbAutoConfiguration
com.north.lat.service.SaveTheWorldService=\
com.north.lat.service.impl.CommonSaveTheWorldServiceImpl,com.north.lat.service.impl.HeroSaveTheWorldImpl

這樣就完成了,項(xiàng)目結(jié)構(gòu)如下圖所示:

image

那該怎么接入呢? 我們?cè)趧倓偟膕pilat工程接入一下試試:

依賴(lài)jar包,這樣就完成接入了

<dependency>
            <groupId>com.north.lat</groupId>
            <artifactId>niubility-spring-starter</artifactId>
            <version>1.0-SNAPSHOT</version>
   </dependency></pre>

所謂的完成接入是指, spring中已經(jīng)注入了SaveTheWorldService的所有實(shí)現(xiàn), 即CommonSaveTheWorldServiceImpl和HeroSaveTheWorldImpl. 我們?cè)赾ontroller中調(diào)用一下

package com.north.spilat.controller; 
import com.north.lat.service.SaveTheWorldService; 
import com.north.lat.service.impl.CommonSaveTheWorldServiceImpl; 
import com.north.lat.service.impl.HeroSaveTheWorldImpl;
 import com.north.spilat.service.DubboDemoService;
 import org.springframework.web.bind.annotation.RequestMapping; 
import org.springframework.web.bind.annotation.RestController; 
import javax.annotation.Resource; 
/** * @author laihaohua */ 
@RestController 
public class HelloWorldController {

    @Resource 
    private CommonSaveTheWorldServiceImpl commonSaveTheWorldService;

    @Resource 
    private HeroSaveTheWorldImpl heroSaveTheWorld;

    @Resource 
     private DubboDemoService dubboDemoService; private static final String HERO = "laihaohua";

    @RequestMapping("/saveTheWorld") 
     public String index(String name) {
        SaveTheWorldService saveTheWorldService; 
        if(HERO.equals(name)){
            saveTheWorldService = heroSaveTheWorld;
        }else {
            saveTheWorldService = commonSaveTheWorldService;
        }
        dubboDemoService.test(name); 
        return saveTheWorldService.saveTheWorld(name);
    }
}

運(yùn)行結(jié)果如下:

image
image

最后,如果后續(xù)版本有了一個(gè)更厲害的SaveTheWorldService的實(shí)現(xiàn), 那我直接就spring.factories里面新增一個(gè)配置,然后調(diào)用方只需要改動(dòng)版本號(hào)即可, 從而實(shí)現(xiàn)了高度解耦

總結(jié)

SPI機(jī)制在各種開(kāi)源框架中都是非常常見(jiàn)的, 有的簡(jiǎn)單點(diǎn)的就直接采用了JDK中的ServiceLoader,如IntelliJ IDEA的插件開(kāi)發(fā)中就應(yīng)用到了. 有些框架復(fù)雜一點(diǎn)的, JDK的SPI已經(jīng)滿(mǎn)足不了, 就自己改造一下, 如spring-boot的SpringFactoriesLoader和dubbo中的ExtensionLoader等, 但是其實(shí)背后的原理都是大同小異.

因此, 了解熟悉一下這些機(jī)制, 一方面可以讓我們更清楚開(kāi)源框架的運(yùn)行原理,少走彎路; 另一方面,也可以作為我們?nèi)粘?xiě)代碼和系統(tǒng)設(shè)計(jì)的一種參考,從而寫(xiě)出更加優(yōu)雅的代碼.

最后,畢竟個(gè)人認(rèn)知有限, 若文中有錯(cuò)誤之處,還望提出,謝謝大家支持。

原文:https://www.cnblogs.com/lhh-north/p/9571441.html
本文所有代碼可以參見(jiàn):
https://github.com/NorthWard/spilat
https://github.com/NorthWard/niubility-spring-starter

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容