SpringBoot 自動配置源碼分析

1. SpringBoot 項目快速搭建

  1. 創建一個 maven 工程(jar)
  2. 導入SpringBoot 相關的依賴
	<parent>
	    <groupId>org.springframework.boot</groupId>
	    <artifactId>spring-boot-starter-parent</artifactId>
	    <version>2.1.12.RELEASE</version>
	</parent>
	<dependencies>
	    <dependency>
	        <groupId>org.springframework.boot</groupId>
	        <artifactId>spring-boot-starter-web</artifactId>
	    </dependency>
	</dependencies>
  1. 編寫一個主程序,啓動 SpringBoot 應用
package com.wangzhao;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author wangzhao
 * @date 2020/6/29 17:43
 */

// @SpringBootApplication 來標註一個主程序類,說明這是一個Spring Boot應用
@SpringBootApplication
public class HelloWorldMainApplication {

    public static void main(String[] args) {
        SpringApplication.run(HelloWorldMainApplication.class, args);
    }

}
  1. 編寫 Controller
package com.wangzhao.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author wangzhao
 * @date 2020/6/29 17:47
 */
@RestController
public class HelloController {

    @RequestMapping("/hello")
    public String sayHello() {
        return "Hello World!";
    }
    
}

  1. 運行主程序測試

在這裏插入圖片描述
6. 項目整體結構
在這裏插入圖片描述

       如上,便成功啓動了一個SpringBoot的項目,可以看到,相比於Spring而言,SpringBoot使用非常簡單,快速,並且我們幾乎沒有進行任何配置文件的填寫。

2. HelloWorld 探究

2.1 pom.xml

2.1.1 父項目

       Hello World其所依賴的父項目爲:spring-boot-starter-parent

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.9.RELEASE</version>
  </parent>

       我們可以看一下spring-boot-starter-parent做了哪些事情

	<properties>
	    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
	    <java.version>1.8</java.version>
	    <resource.delimiter>@</resource.delimiter>
	    <maven.compiler.source>${java.version}</maven.compiler.source>
	    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	    <maven.compiler.target>${java.version}</maven.compiler.target>
    </properties>
  1. 使用 UTF-8 格式編碼
  2. 定義了 Java 編譯版本爲 1.8

       除此之外,spring-boot-starter-parent其所依賴的父項目爲spring-boot-dependencies

	<parent>
	    <groupId>org.springframework.boot</groupId>
	    <artifactId>spring-boot-dependencies</artifactId>
	    <version>2.1.12.RELEASE</version>
	    <relativePath>../../spring-boot-dependencies</relativePath>
   </parent>

       

	<properties>
	    <activemq.version>5.15.11</activemq.version>
	    <antlr2.version>2.7.7</antlr2.version>
	    <appengine-sdk.version>1.9.77</appengine-sdk.version>
	    <artemis.version>2.6.4</artemis.version>
	    <aspectj.version>1.9.5</aspectj.version>
	    <assertj.version>3.11.1</assertj.version>
	    <atomikos.version>4.0.6</atomikos.version>
	    <bitronix.version>2.1.4</bitronix.version>
	    ......
    </properties>
    
    <dependencyManagement>
	    <dependencies>
	      <dependency>
	        <groupId>org.springframework.boot</groupId>
	        <artifactId>spring-boot</artifactId>
	        <version>2.1.12.RELEASE</version>
	      </dependency>
	      <dependency>
	        <groupId>org.springframework.boot</groupId>
	        <artifactId>spring-boot-test</artifactId>
	        <version>2.1.12.RELEASE</version>
	      </dependency>
	      ......
	   </dependencies>
	</dependencyManagement>
      

       其相當於SpringBoot版本仲裁中心

       他提供了jar包的版本管理,以及Spring框架和其他第三方組件jar包的依賴管理。

2.1.2 導入的依賴

       回到我們項目的pom文件中,導入了該依賴。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

       讓我們進入到spring-boot-starter-webpom文件中,可以看到導入的都是web相關的模塊,並且還內置了一個tomcat

<dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
      <version>2.1.12.RELEASE</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-json</artifactId>
      <version>2.1.12.RELEASE</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-tomcat</artifactId>
      <version>2.1.12.RELEASE</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.hibernate.validator</groupId>
      <artifactId>hibernate-validator</artifactId>
      <version>6.0.18.Final</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>5.1.13.RELEASE</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.1.13.RELEASE</version>
      <scope>compile</scope>
    </dependency>
  </dependencies>

       spring-boot-starterspring-boot場景啓動器,其幫我們導入了相關場景模塊正常運行所依賴的組件;

       Spring Boot將所有的功能場景都抽取出來,做成一個個的starter(啓動器),只需要在項目裏面引入這些starter相關場景的所有依賴都會導入進來。要用什麼功能,就導入什麼場景的啓動器。

2.2 主啓動類

// @SpringBootApplication 來標註一個主程序類,說明這是一個Spring Boot應用
@SpringBootApplication
public class HelloWorldMainApplication {

    public static void main(String[] args) {
        SpringApplication.run(HelloWorldMainApplication.class, args);
    }

}

       @SpringBootApplicationSpringBoot應用標註在某個類上說明這個類是SpringBoot的主配置類,SpringBoot就應該運行這個類的main()方法啓動SpringBoot應用。

2.2.1 @SpringBootApplication

       進入到@SpringBootApplication內,觀察其做了哪些工作:

package org.springframework.boot.autoconfigure;


@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

	// 根據class來排除特定的類,使其不能加入spring容器,傳入參數value類型是class類型。
	@AliasFor(annotation = EnableAutoConfiguration.class)
	Class<?>[] exclude() default {};

	// 根據classname 來排除特定的類,使其不能加入spring容器,傳入參數value類型是class的全類名字符串數組。
	@AliasFor(annotation = EnableAutoConfiguration.class)
	String[] excludeName() default {};

	// 指定掃描包,參數是包名的字符串數組。
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
	String[] scanBasePackages() default {};

	// 掃描特定的包,參數類似是Class類型數組。
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
	Class<?>[] scanBasePackageClasses() default {};

}

       可以看到@SpringBootApplication是一個複合註解,包括@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan

  • @SpringBootConfigurationSpringBoot的配置類,標註在某個類上,表示這是一個SpringBoot的配置類。
package org.springframework.boot;

// 配置類的作用等同於配置文件,配置類也是容器中的一個對象
@Configuration
public @interface SpringBootConfiguration {
}
  • @EnableAutoConfiguration:開啓自動配置功能,以前由我們需要配置的東西,現在由SpringBoot幫我們自動配置,這個註解就是Springboot能實現自動配置的關鍵
  • @ComponentScan:這個註解是組件掃描這個是我們最熟悉的註解,即使沒有使用過註解也經常在Spring的配置文件中使用過<context:component-scan base-package="com.xxx.xxx"/>, 組件掃描就是掃描指定的包下的類,並加載符合條件的組件。

3. @EnableAutoConfiguration

package org.springframework.boot.autoconfigure;

// 自動配置包
@AutoConfigurationPackage

// Spring的底層註解@Import,給容器中導入一個組件;
// 導入的組件是AutoConfigurationPackages.Registrar.class 
@Import(AutoConfigurationImportSelector.class)

// 告訴SpringBoot開啓自動配置功能,這樣自動配置才能生效。
public @interface EnableAutoConfiguration {

	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	// 返回不會被導入到 Spring 容器中的類
	Class<?>[] exclude() default {};

	// 返回不會被導入到 Spring 容器中的類名
	String[] excludeName() default {};

}

       Spring 中有很多以Enable開頭的註解,其作用就是藉助@Import來收集並註冊特定場景相關的Bean,並加載到IOC容器。@EnableAutoConfiguration就是藉助@Import來收集所有符合自動配置條件的bean定義,並加載到IoC容器。

       @EnableAutoConfiguration就是藉助@Import來收集所有符合自動配置條件的Bean定義,並加載到IOC容器。

3.1 @AutoConfigurationPackage

       @AutoConfigurationPackage:自動配置包,它也是一個組合註解,其中最重要的註解是@Import(AutoConfigurationPackages.Registrar.class),它是Spring框架的底層註解,它的作用就是給容器中導入某個組件類,例如@Import(AutoConfigurationPackages.Registrar.class),它就是將Registrar這個組件類導入到容器中,可查看Registrar類中registerBeanDefinitions方法,這個方法就是導入組件類的具體實現。

package org.springframework.boot.autoconfigure;

@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
	// 將註解標註的元信息傳入,獲取到相應的包名
	register(registry, new PackageImport(metadata).getPackageName());
}

在這裏插入圖片描述
       可以看到AnnotationMetadata註解標註注的元信息中包含了使用了哪些註解,相應的註解作用在哪個類上

       我們對new PackageImport(metadata).getPackageName()進行檢索,看看其結果是什麼?

在這裏插入圖片描述

       因此可以得知使用@AutoConfigurationPackage註解就是將主程序類所在包及所有子包下的組件到掃描到Spring容器中。

3.1.1 AutoConfigurationImportSelector

       @Import({AutoConfigurationImportSelector.class}):將AutoConfigurationImportSelector這個類導入到Spring容器中,AutoConfigurationImportSelector可以幫助Springboot應用將所有符合條件的@Configuration配置都加載到當前SpringBoot創建並使用的IOC容器(ApplicationContext)中。

       AutoConfigurationImportSelectorImportSelector 接口的實現類,而 ImportSelector 接口中的selectImports方法將返回的全限定類名對應的類交給 Spring 容器管理

       其不光實現了ImportSelector接口,還實現了很多其它的Aware接口,分別表示在某個時機會被回調。

在這裏插入圖片描述

public interface ImportSelector {
	String[] selectImports(AnnotationMetadata importingClassMetadata);
}

       我們可以知道,所有的aware都優先於selectImports方法執行,也就是說selectImports方法最後執行,那麼在它執行的時候所有需要的資源都已經獲取到了(AutoConfigurationImportSelector的四個成員變量)

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

	private ConfigurableListableBeanFactory beanFactory;

	private Environment environment;

	private ClassLoader beanClassLoader;

	private ResourceLoader resourceLoader;
	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		// 獲得自動配置元信息,需要傳入beanClassLoader這個類加載器
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
				annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

深入研究 loadMetadata 方法

	// 文件中爲需要加載的配置類的類路徑
	protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties";

	public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
		return loadMetadata(classLoader, PATH);
	}

	static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
		try {

            // 讀取spring-boot-autoconfigure-2.1.12.RELEASE.jar包中
            // spring-autoconfigure-metadata.properties的信息生成urls枚舉對象
			Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
					: ClassLoader.getSystemResources(path);
			Properties properties = new Properties();

            //解析urls枚舉對象中的信息封裝成properties對象並加載
			while (urls.hasMoreElements()) {
				properties.putAll(PropertiesLoaderUtils
						.loadProperties(new UrlResource(urls.nextElement())));
			}

            //根據封裝好的properties對象生成AutoConfigurationMetadata對象返回
			return loadMetadata(properties);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException(
					"Unable to load @ConditionalOnClass location [" + path + "]", ex);
		}
	}

在這裏插入圖片描述
在這裏插入圖片描述

深入研究 getAutoConfigurationEntry 方法

	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);
		// 應用 exclusion 屬性
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
		
		// 應用過濾器 AutoConfigurationImportFilter,
        // 對於 spring boot autoconfigure,定義了一個需要被應用的過濾器 :
        // org.springframework.boot.autoconfigure.condition.OnClassCondition,
        // 此過濾器檢查候選配置類上的註解@ConditionalOnClass,如果要求的類在classpath
        // 中不存在,則這個候選配置類會被排除掉
		configurations = filter(configurations, autoConfigurationMetadata);
		
		// 現在已經找到所有需要被應用的候選配置類
        // 廣播事件 AutoConfigurationImportEvent 
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}
深入 getCandidateConfigurations 方法

       這個方法中有一個重要方法loadFactoryNames,這個方法是讓SpringFactoryLoader去加載一些組件的名字。

	protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
	 // 這個方法需要傳入兩個參數getSpringFactoriesLoaderFactoryClass()和getBeanClassLoader()
     // getSpringFactoriesLoaderFactoryClass()這個方法返回的是EnableAutoConfiguration.class
     // getBeanClassLoader()這個方法返回的是beanClassLoader(類加載器)
		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;
	}

       繼續點開loadFactory方法

	public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
        
        //獲取出入的鍵
        String factoryClassName = factoryClass.getName();
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
              
                //如果類加載器不爲null,則加載類路徑下spring.factories文件,將其中設置的配置類的全路徑信息封裝 爲Enumeration類對象
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();

                //循環Enumeration類對象,根據相應的節點信息生成Properties對象,通過傳入的鍵獲取值,在將值切割爲一個個小的字符串轉化爲Array,方法result集合中
                while(urls.hasMoreElements()) {
                    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;
       }
   }
}

3.2 自動配置原理

  1. SpringBoot啓動的時候加載主配置類,開啓了自動配置功能 @EnableAutoConfiguration
  2. @EnableAutoConfiguration 作用:
  • 利用 EnableAutoConfigurationImportSelector 給容器中導入一些組件
  • 可以查看 selectImports() 方法的內容:
List<String> configurations = this.getCandidateConfigurations(annotationMetadata,attributes);
  • 上面代碼的作用是獲取候選的配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    
   List<String> configurations = SpringFactoriesLoader.loadFactoryNames();
    
   // 掃描所有 jar 包類路徑下  META-INF/spring.factories
   // 把掃描到的這些文件的內容包裝成 properties 對象
   // 從properties中獲取到 EnableAutoConfiguration.class類(類名對應的值),把他們添加到容器中
# 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,\
......

       每一個這樣的 xxxAutoConfiguration類都是容器中的一個組件,都加入到容器之中。用它們來做自動配置。

  1. 每一個自動配置類進行自動配置功能
  2. HttpEncodingAutoConfigurationHttp編碼自動配置)爲例解釋自動配置原理
// 表示這是一個配置類,和以前編寫的配置文件一樣,也可以給容器中添加組件
@Configuration

// 啓動指定類的ConfigurationProperties功能;將配置文件中對應的值和HttpEncodingProperties綁定起來;
@EnableConfigurationProperties({HttpEncodingProperties.class}) 

// Spring底層@Conditional註解,根據不同的條件,如果滿足指定的條件,整個配置類裏面的配置就會生效。
// 判斷當前應用是否是web應用,如果是,當前配置類生效。並把HttpEncodingProperties加入到 ioc 容器中
@ConditionalOnWebApplication

// 判斷當前項目有沒有這個CharacterEncodingFilter : SpringMVC中進行亂碼解決的過濾器
@ConditionalOnClass({CharacterEncodingFilter.class})

// 判斷配置文件中是否存在某個配置 spring.http.encoding.enabled 如果不存在,判斷也是成立的
// matchIfMissing = true 表示即使我們配置文件中不配置spring.http.encoding.enabled=true,也是默認生效的
@ConditionalOnProperty(
    prefix = "spring.http.encoding",
    value = {"enabled"},
    matchIfMissing = true
)
public class HttpEncodingAutoConfiguration {
    
    // 它已經和SpringBoot配置文件中的值進行映射了
    private final HttpEncodingProperties properties;
    
    // 只有一個有參構造器的情況下,參數的值就會從容器中拿
    public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
        this.properties = properties;
    }
    
    @Bean	//給容器中添加一個組件,這個組件中的某些值需要從properties中獲取
    @ConditionalOnMissingBean({CharacterEncodingFilter.class})	//判斷容器中沒有這個組件
    public CharacterEncodingFilter characterEncodingFilter() {
        CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
        filter.setEncoding(this.properties.getCharset().name());
        filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
        filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
        return filter;
    }

       根據當前不同的條件判斷,決定這個配置類是否生效。

       一旦這個配置類生效,這個配置類就會給容器中添加各種組件;這些組件的屬性是從對應的properties類中獲取的,這些類裏面的每一個屬性又是和配置文件綁定的。

# 我們能配置的屬性都是來源於這個功能的properties類
spring.http.encoding.enabled=true
spring.http.encoding.charset=utf-8
spring.http.encoding.force=true
  1. 所有在配置文件中能配置的屬性都是在 xxxProperties 類中封裝着,配置文件能配置什麼就可以參照某個功能對應的這個屬性類。
// 從配置文件中獲取指定的值和bean的屬性進行綁定
@ConfigurationProperties(prefix = "spring.http.encoding")
public class HttpEncodingProperties {
    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

3.3 精髓

  1. SpringBoot 啓動會加載大量的自動配置類

  2. 我們看我們需要實現的功能有沒有SpringBoot默認寫好的自動配置類

  3. 我們再來看這個自動配置類中到底配置了哪些組件;(只要我們有我們要用的組件,我們就不需要再來配置了)

  4. 給容器中自動配置類添加組件的時候,會從properties類中獲取某些屬性,我們就可以在配置文件中指定這些屬性的值。

       xxxAutoConfiguration:自動配置類,用於給容器中添加組件從而代替之前我們手動完成大量繁瑣的配置。

       xxxProperties : 封裝了對應自動配置類的默認屬性值,如果我們需要自定義屬性值,只需要根據xxxProperties尋找相關屬性在配置文件設值即可。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章