為什麼要使用SpringBoot?使用SpringBoot的最大好處是什麼?

使用SpringBoot的最大好處就是簡化配置,它實現了自動化配置。

這裡以SpringBoot 2.1.4.RELEASE版本和Spring 5.1.6.RELEASE版本為例。

API文檔:https://docs.spring.io/spring-boot/docs/current/api/

自動化配置的原理如下:

一個SpringBoot構建的項目都會有一個入口啟動類,其中有個最重要的註解就是@SpringBootApplication,其源碼如下:

<code>@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
@AliasFor(
annotation = EnableAutoConfiguration.class
)
Class>[] exclude() default {};
@AliasFor(
annotation = EnableAutoConfiguration.class
)
String[] excludeName() default {};
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackages"
)
String[] scanBasePackages() default {};
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackageClasses"

)
Class>[] scanBasePackageClasses() default {};
}
/<code>

在SpringBootApplication類上有一個重要的註解@EnableAutoConfiguration,它就是實現自動化配置的核心。當SpringBoot項目啟動的時候,就會調用@EnableAutoConfiguration來進一步加載系統所需的一些配置信息,完成自動化配置。

@EnableAutoConfiguration的源碼如下:

<code>@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class>[] exclude() default {};
String[] excludeName() default {};
}
/<code>

在EnableAutoConfiguration類中,它使用@Import註解來導入配置類AutoConfigurationImportSelector。

使用@Import註解可以導入三種類型的配置類,如下:

(1)直接導入配置類:@Import({xxxConfiguration.class})(2)依據條件選擇配置類:@Import({xxxSelector.class})(3)動態註冊 Bean:@Import({xxxRegistrar.class})

我們進入查看AutoConfigurationImportSelector類的源碼,(由於源碼太多,在此不再展示),其中用到了一個重要的類SpringFactoriesLoader,該類位於org.springframework.core.io.support包下,它才是真正加載項目所需要的jar包的類,它主要用於加載 classpath下所有 JAR 文件的 META-INF/spring.factories 文件,並分析出其中定義的工廠類。這些工廠類進而被啟動邏輯使用,應用於進一步初始化工作。

SpringFactoriesLoader類是spring框架自己使用的內部工具類,本身被聲明為 final,表示不可以被其他類繼承。

SpringFactoriesLoader類的源碼如下:

<code>//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.core.io.support;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Map.Entry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.io.UrlResource;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* SpringFactoriesLoader#loadFactories設計用於加載和實例化指定類型的工廠,這些工廠類型的定義
* 來自classpath中多個JAR包內常量FACTORIES_RESOURCE_LOCATION所指定的那些spring.factories文件。
* spring.factories文件的格式必須是屬性文件格式,每條屬性的key必須是接口或者抽象類的全限定名,

* 而屬性值value是一個逗號分割的實現類的名稱。
*/
public final class SpringFactoriesLoader {
/*
* 要加載的資源路徑,該常量定義了該工具類要從每個jar包中提取的工廠類定義屬性文件的相對路徑
* 在classpath中的多個JAR中,要掃描的工廠配置文件的在本JAR包中的路徑。
* 實際上,Springboot的每個 autoconfigure包都包含spring.factories這個配置文件。
*/
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>> cache = new ConcurrentReferenceHashMap();
private SpringFactoriesLoader() {
}
/**
* @param factoryClass 工廠所屬接口/抽象類全限定名稱
* @param classLoader 所要使用的類加載器
*
* 該方法會讀取classpath上所有的jar包中的所有 META-INF/spring.factories 屬性文件,找出其中定義的匹配類型 factoryClass 的工廠類,
* 然後創建每個工廠類的對象/實例,並返回這些工廠類對象/實例的列表
*/
public static List loadFactories(Class factoryClass, @Nullable ClassLoader classLoader) {
Assert.notNull(factoryClass, "'factoryClass' must not be null");
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
//加載類型為factoryClass的工廠的名稱,其實是一個個的全限定類名,使用指定的classloader:classLoaderToUse

List<string> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
if (logger.isTraceEnabled()) {
logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
}
List result = new ArrayList(factoryNames.size());
Iterator var5 = factoryNames.iterator();
// 實例化所加載的每個工廠類
while(var5.hasNext()) {
String factoryName = (String)var5.next();
result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
}
//對工廠類進行排序
AnnotationAwareOrderComparator.sort(result);
return result;
}
/**
*
* @param factoryClass 工廠所屬接口/抽象類全限定名稱
* @param classLoader 類加載器
* @return
*
* 該方法會讀取classpath上所有的jar包中的所有 META-INF/spring.factories 屬性文件,找出其中定義的匹配類型 factoryClass 的工廠類,
* 然後並返回這些工廠類的名字列表,注意是包含包名的全限定名。
*/
public static List<string> loadFactoryNames(Class> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
// 1. 使用指定的classloader掃描classpath上所有的JAR包中的文件META-INF/spring.factories,加載其中的多值工廠屬性定義,使用多值Map的形式返回,
// 2. 返回多值Map中key為factoryClassName的工廠名稱列表,如果沒有相應的entry,返回空列表而不是返回null
return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
/**
* @param classLoader 類加載器

*
* 使用指定的classloader掃描classpath上所有的JAR包中的文件META-INF/spring.factories,加載其中的多值
* 工廠屬性定義,使用多值Map的形式返回
**/
private static Map<string>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<string> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
// 掃描classpath上所有JAR中的文件META-INF/spring.factories
Enumeration urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
while(urls.hasMoreElements()) {
// 找到的每個META-INF/spring.factories文件都是一個Properties文件,將其內容
// 加載到一個 Properties 對象然後處理其中的每個屬性
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry, ?> entry = (Entry)var6.next();
// 獲取工廠類名稱(接口或者抽象類的全限定名)
String factoryClassName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryName = var9[var11];
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}

/**
* @param instanceClassName 工廠實現類全限定名稱
* @param factoryClass 工廠所屬接口/抽象類全限定名稱
* @param classLoader 所要使用的類加載器
**/
private static T instantiateFactory(String instanceClassName, Class 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() + "]");
} else {
return ReflectionUtils.accessibleConstructor(instanceClass, new Class[0]).newInstance();
}
} catch (Throwable var4) {
throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), var4);
}
}
}
/<string>/<string>/<string>
/<string>
/<classloader>/<code>

一般情況下,springboot提供的一些JAR包裡面會帶有文件META-INF/spring.factories,然後在Springboot啟動的時候,根據啟動階段不同的需求,框架就會多次調用SpringFactoriesLoader加載相應的工廠配置信息。

使用了註解@EnableAutoConfiguration時,就會觸發對SpringFactoriesLoader.loadFactoryNames()的調用。

看一下spring.factories所在的位置:

為什麼要使用SpringBoot?使用SpringBoot的最大好處是什麼?

部分內容如下:

<code># Initializers
org.springframework.context.ApplicationContextInitializer=\\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners
org.springframework.context.ApplicationListener=\\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
/<code>

以上就是SpringBoot實現自動化配置的原理,或許你看到的源碼會與我的不相同,那有可能是jar版本的不一致。

總結一下使用SpringBoot的好處:

(1)簡化配置,不需要編寫太多的xml配置文件;

(2)基於Spring構建,使開發者快速入門,門檻很低;

(3)SpringBoot可以創建獨立運行的應用而不需要依賴於容器;

(4)內置tomcat服務器,不需要打包成war包,可以直接放到tomcat中運行;

(5)提供maven極簡配置,以及可視化的相關監控功能,比如性能監控,應用的健康程度等;

(6)為微服務SpringCloud奠定了基礎,使得微服務的構建變得簡單;

(7)Spring可以整合很多各式各樣的框架,並能很好的集成;

(8)活躍的社區與論壇,以及豐富的開發文檔;


最後在這裡給大家免費分享一波福利,都是視頻資料,裡面就包涵了Java高併發、分佈式、微服務、高性能、源碼分析、JVM等技術資料,感興趣的私信我回復【java】

最後祝福所有遇到瓶疾且不知道怎麼辦的Java程序員們,在往後的工作與面試中一切順利。

注:
作者:霜花似雪
原文:http://www.imooc.com/article/287576


分享到:


相關文章: