Spring Boot factories機制

Spring factories的加載方式類似于SPI

  1. 都是在頂層jar包中定義接口規(guī)范
  2. 具體接口實現(xiàn)交給項目按需加載
  3. 通過配置文件(spring.factories)朴皆,定義對應(yīng)接口的具體實現(xiàn)類
  4. 都是通過線程上下文類加載器的方式來加載具體的實現(xiàn)類
  5. 關(guān)于SPI的機制可以參考SPI加載機制和線程上下文類加載器

Spring Boot中具體的實現(xiàn)

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = getClassLoader();
    // Use names and ensure unique to protect against duplicates
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}
  1.  //獲取線程上下文類加載器
     ClassLoader classLoader = getClassLoader();
    
  2.  //使用類加載器獲取對應(yīng)接口實現(xiàn)類的binary name咬像,具體的實現(xiàn)在SpringFactoriesLoader類中完成。
     SpringFactoriesLoader.loadFactoryNames(type, classLoader)
    
    /**
    * General purpose factory loading mechanism for internal use within the framework.
    *
    * <p>{@code SpringFactoriesLoader} {@linkplain #loadFactories loads} and instantiates
    * factories of a given type from {@value #FACTORIES_RESOURCE_LOCATION} files which
    * may be present in multiple JAR files in the classpath. The {@code spring.factories}
    * file must be in {@link Properties} format, where the key is the fully qualified
    * name of the interface or abstract class, and the value is a comma-separated list of
    * implementation class names. For example:
    *
    * <pre class="code">example.MyService=example.MyServiceImpl1,example.MyServiceImpl2</pre>
    *
    * where {@code example.MyService} is the name of the interface, and {@code MyServiceImpl1}
    * and {@code MyServiceImpl2} are two implementations.
    *
    * @author Arjen Poutsma
    * @author Juergen Hoeller
    * @author Sam Brannen
    * @since 3.2
    */
    public final class SpringFactoriesLoader {
    
        /**
        * The location to look for factories.
        * <p>Can be present in multiple JAR files.
        */
        public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    
    
        private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
    
        private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();
    
    
        private SpringFactoriesLoader() {
        }
    
    
        /**
        * Load and instantiate the factory implementations of the given type from
        * {@value #FACTORIES_RESOURCE_LOCATION}, using the given class loader.
        * <p>The returned factories are sorted through {@link AnnotationAwareOrderComparator}.
        * <p>If a custom instantiation strategy is required, use {@link #loadFactoryNames}
        * to obtain all registered factory names.
        * @param factoryClass the interface or abstract class representing the factory
        * @param classLoader the ClassLoader to use for loading (can be {@code null} to use the default)
        * @throws IllegalArgumentException if any factory implementation class cannot
        * be loaded or if an error occurs while instantiating any factory
        * @see #loadFactoryNames
        */
        public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
            Assert.notNull(factoryClass, "'factoryClass' must not be null");
            ClassLoader classLoaderToUse = classLoader;
            if (classLoaderToUse == null) {
                classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
            }
            List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
            if (logger.isTraceEnabled()) {
                logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
            }
            List<T> result = new ArrayList<>(factoryNames.size());
            for (String factoryName : factoryNames) {
                result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
            }
            AnnotationAwareOrderComparator.sort(result);
            return result;
        }
    
        /**
        * Load the fully qualified class names of factory implementations of the
        * given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
        * class loader.
        * @param factoryClass the interface or abstract class representing the factory
        * @param classLoader the ClassLoader to use for loading resources; can be
        * {@code null} to use the default
        * @throws IllegalArgumentException if an error occurs while loading factory names
        * @see #loadFactories
        */
        public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
            String factoryClassName = factoryClass.getName();
            return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
        }
    
        private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
            MultiValueMap<String, String> result = cache.get(classLoader);
            if (result != null) {
                return result;
            }
    
            try {
                Enumeration<URL> urls = (classLoader != null ?
                        classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                        ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
                result = new LinkedMultiValueMap<>();
                while (urls.hasMoreElements()) {
                    URL url = urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    for (Map.Entry<?, ?> entry : properties.entrySet()) {
                        String factoryClassName = ((String) entry.getKey()).trim();
                        for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                            result.add(factoryClassName, factoryName.trim());
                        }
                    }
                }
                cache.put(classLoader, result);
                return result;
            }
            catch (IOException ex) {
                throw new IllegalArgumentException("Unable to load factories from location [" +
                        FACTORIES_RESOURCE_LOCATION + "]", ex);
            }
        }
    
        @SuppressWarnings("unchecked")
        private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) {
            try {
                Class<?> instanceClass = ClassUtils.forName(instanceClassName, classLoader);
                if (!factoryClass.isAssignableFrom(instanceClass)) {
                    throw new IllegalArgumentException(
                            "Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]");
                }
                return (T) ReflectionUtils.accessibleConstructor(instanceClass).newInstance();
            }
            catch (Throwable ex) {
                throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), ex);
            }
        }
    
    }
    
    //通過此處具體定義了spring factories配置文件的路徑位于`META-INF/spring.factories`
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    
    Spring boot在啟動的時候验辞,默認(rèn)會有三個spring.factories文件,分別位于
    • spring-boot
    • spring-actuator-autoconfigure
    • spring-beans
  3. spring.factories具體內(nèi)容
    # Application Context Initializers
    org.springframework.context.ApplicationContextInitializer=\
    org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
    org.springframework.boot.context.ContextIdApplicationContextInitializer,\
    org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
    org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
    
    這一段配置是關(guān)于Application Context Initializers归形,對應(yīng)的接口是org.springframework.context.ApplicationContextInitializer震庭,下邊為該接口對應(yīng)的實現(xiàn)類。
    spring.factories的格式為:
    • Key 是接口全限定Class name寇仓;
    • Value 是key對應(yīng)實現(xiàn)類的全限定Class name举户,用逗號分隔。
  4.  //創(chuàng)建對應(yīng)實現(xiàn)類的實例
     createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    
    private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
            ClassLoader classLoader, Object[] args, Set<String> names) {
        List<T> instances = new ArrayList<>(names.size());
        //names就是剛才從SpringFactoriesLoader獲取到的具體實現(xiàn)類的binary name
        for (String name : names) {
            try {
                //通過之前獲取到的線程上下文類加載器去加載對應(yīng)的binary name
                Class<?> instanceClass = ClassUtils.forName(name, classLoader);
                Assert.isAssignable(type, instanceClass);
                Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
                T instance = (T) BeanUtils.instantiateClass(constructor, args);
                instances.add(instance);
            }
            catch (Throwable ex) {
                throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
            }
        }
        return instances;
    }
    
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末遍烦,一起剝皮案震驚了整個濱河市俭嘁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌服猪,老刑警劉巖供填,帶你破解...
    沈念sama閱讀 223,207評論 6 521
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拐云,死亡現(xiàn)場離奇詭異,居然都是意外死亡近她,警方通過查閱死者的電腦和手機叉瘩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,455評論 3 400
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來粘捎,“玉大人薇缅,你說我怎么就攤上這事≡苣ィ” “怎么了泳桦?”我有些...
    開封第一講書人閱讀 170,031評論 0 366
  • 文/不壞的土叔 我叫張陵,是天一觀的道長娩缰。 經(jīng)常有香客問我灸撰,道長,這世上最難降的妖魔是什么漆羔? 我笑而不...
    開封第一講書人閱讀 60,334評論 1 300
  • 正文 為了忘掉前任梧奢,我火速辦了婚禮狱掂,結(jié)果婚禮上演痒,老公的妹妹穿的比我還像新娘。我一直安慰自己趋惨,他們只是感情好鸟顺,可當(dāng)我...
    茶點故事閱讀 69,322評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著器虾,像睡著了一般讯嫂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上兆沙,一...
    開封第一講書人閱讀 52,895評論 1 314
  • 那天欧芽,我揣著相機與錄音,去河邊找鬼葛圃。 笑死千扔,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的库正。 我是一名探鬼主播曲楚,決...
    沈念sama閱讀 41,300評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼褥符!你這毒婦竟也來了龙誊?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,264評論 0 277
  • 序言:老撾萬榮一對情侶失蹤喷楣,失蹤者是張志新(化名)和其女友劉穎趟大,沒想到半個月后鹤树,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,784評論 1 321
  • 正文 獨居荒郊野嶺守林人離奇死亡护昧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,870評論 3 343
  • 正文 我和宋清朗相戀三年魂迄,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惋耙。...
    茶點故事閱讀 40,989評論 1 354
  • 序言:一個原本活蹦亂跳的男人離奇死亡捣炬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出绽榛,到底是詐尸還是另有隱情湿酸,我是刑警寧澤,帶...
    沈念sama閱讀 36,649評論 5 351
  • 正文 年R本政府宣布灭美,位于F島的核電站推溃,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏届腐。R本人自食惡果不足惜铁坎,卻給世界環(huán)境...
    茶點故事閱讀 42,331評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望犁苏。 院中可真熱鬧硬萍,春花似錦、人聲如沸围详。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,814評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽助赞。三九已至买羞,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間雹食,已是汗流浹背畜普。 一陣腳步聲響...
    開封第一講書人閱讀 33,940評論 1 275
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留群叶,地道東北人吃挑。 一個月前我還...
    沈念sama閱讀 49,452評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像盖呼,于是被迫代替她去往敵國和親儒鹿。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,995評論 2 361

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