Spring Boot相對於Spring的一大改變或者優勢來說就是“約定大於配置”的思想,不像Spring一樣所有的配置都需要我們自己去實現,Spring Boot集成了許多默認的配置。拿Spring MVC來舉例,原來Spring時代是通過寫兩個XML配置文件來實現的,一個web.xml,另一個applicationContext.xml。這些文件內容複雜,且大部分情況下不需要改變,在各個項目中的遷移也只是複製粘貼裏面的代碼而已,這無疑增加了使用成本。而在Spring Boot中,只需要引入相關spring-boot-starter-web依賴即可,其他的配置都不需要。即使有需要其他配置的地方,統一在application.properties配置文件中進行配置即可,該文件寫法是類似於json的鍵值對的格式,不像XML格式那樣的重量級。
那麼既然不需要相關配置,Spring Boot是如何實現自動裝配類的呢?如何在項目啓動的時候將需要加載的類都注入到Spring的IoC容器中?本文將探究這個問題。
通常在Spring Boot的啓動類上會加上@SpringBootApplication的註解,如下所示:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
其註解主要是由三個子註解構成的,分別是@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan:
@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 {
//...
}
這樣我們可以直接使用@SpringBootApplication註解而不再需要使用以上三個註解來標識啓動類了。其中@EnableAutoConfiguration是開啓自動裝配的功能,該註解的代碼如下所示:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
//...
}
由上所示,@EnableAutoConfiguration註解需要引入AutoConfigurationImportSelector這個類或者其子類,如果沒有找到,則自動裝配失敗。
而在上述啓動時會調用SpringApplication的run方法。該方法會最終調用如下所示的重載run方法:
/**
* 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) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
以上方法會創建/刷新ApplicationContext、初始化Environment、listeners等一系列操作。在第23行代碼中,其會調用getSpringFactoriesInstances方法,同時會將SpringBootExceptionReporter.class這個參數傳入進去:
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;
}
在該方法中會調用SpringFactoriesLoader的loadFactoryNames方法。SpringFactoriesLoader類的作用是利用工廠的加載機制來讀取裝配資源的類,其部分源碼如下所示:
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 Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();
//...
/**
* 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);
}
}
//...
}
其中可以看到FACTORIES_RESOURCE_LOCATION這個常量,它就是讀取配置資源的文件地址,也就是說會從spring.factories文件中讀取所有需要注入的類出來,每一個jar包下基本上都會有一個spring.factories配置文件。而上面源碼中第25行的loadFactoryNames方法會調用第30行的loadSpringFactories方法,該方法的作用是讀取加載進來的所有jar包下的spring.factories配置文件中的內容,將其放到一個本地緩存cache中。緩存的意義在於第一次調用該方法時會讀取spring.factories文件並將讀取中的結果放到緩存中,之後再調用該方法時就不再讀取文件,而直接返回緩存中的內容就行了。
在spring-boot-autoconfigure的jar包下META-INF目錄中的spring.factories文件的部分內容如下:
# 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
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.rest.RestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
//...
由上可以看到,其文件格式由鍵值對組成,每一行末尾的反斜槓表示換行,其他jar包下spring.factories文件的內容也都會有所不同。
而在上面說過的SpringFactoriesLoader類的loadFactoryNames方法中會通過ClassLoader來讀取spring.factories文件中的內容。通過run方法中傳入的SpringBootExceptionReporter.class這個參數,找到文件中鍵是SpringBootExceptionReporter所對應的值的集合,通過反射機制來實例化相關的類再返回即可。如前面所說,這裏是第一次調用SpringFactoriesLoader類的工廠加載機制的地方,後續再調用的話會直接走緩存中的內容。
再回到最開始SpringApplication類中的run方法中的代碼中,之前說的都是基於其中的getSpringFactoriesInstances方法的延展,在該方法執行完畢後,在第28行會調用refreshContext方法,在其中會調用AutoConfigurationImportSelector類的getAutoConfigurationEntry方法,該方法就是自動裝配類的方法入口,而AutoConfigurationImportSelector類前面也說過,是@EnableAutoConfiguration這個註解必須要引入的類。在getAutoConfigurationEntry方法中會調用getCandidateConfigurations方法,這兩個方法的源碼如下:
/**
* Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
* of the importing {@link Configuration @Configuration} class.
* @param autoConfigurationMetadata the auto-configuration metadata
* @param annotationMetadata the annotation metadata of the configuration class
* @return the auto-configurations that should be imported
*/
protected AutoConfigurationEntry getAutoConfigurationEntry(
AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
/**
* Return the auto-configuration class names that should be considered. By default
* this method will load candidates using {@link SpringFactoriesLoader} with
* {@link #getSpringFactoriesLoaderFactoryClass()}.
* @param metadata the source metadata
* @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
* attributes}
* @return a list of candidate configurations
*/
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;
}
getCandidateConfigurations方法中也會像上面說過的getSpringFactoriesInstances方法一樣,調用SpringFactoriesLoader的loadFactoryNames方法來獲取配置文件中的內容,只不過這次傳入的參數是getSpringFactoriesLoaderFactoryClass(),即EnableAutoConfiguration.class。也就是說會將需要自動裝配的類都拿到,通過EnableAutoConfiguration所對應的值的類名的集合。但這次並不會讀取spring.factories配置文件,而是會從緩存中讀取,因爲之前已經調用過loadSpringFactories方法了。讀取出的內容依然會使用反射來實例化,將實例化的結果返回回去並最終完成自動裝配的全過程。這些自動裝配的類會通過一些@ConditionalOnClass、@ConditionalOnMissingClass、@Import之類的註解來加載它們需要的資源。
以上就是Spring Boot完成自動裝配的大致核心流程,總結起來就是利用了SpringFactoriesLoader這個類來實現的工廠加載機制,讀取jar包下的META-INF目錄下的spring.factories配置文件中的內容,然後將需要的類名反射實例化即可。
需要自動裝配的類在以前的Spring時代都是需要我們自己寫的,但現在Spring Boot的自動裝配機制幫我們實現了,只需要引入相關的依賴即可。例如Redis的依賴就是spring-boot-starter-data-redis,引用它就可以了,同時在application.properties配置文件中做些簡單的配置即可,就可以直接用起來了,不再需要自己寫連接客戶端、連接池之類的代碼。在上面的spring.factories配置文件的示例中也出現了像RedisAutoConfiguration這樣的Redis的自動配置類。
更多免費技術資料可關注:annalin1203
Spring Boot自動裝配原理
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.