SpringBoot 2.X課程學習 | 第三篇:自動配置(Auto-configuration)

1、auto-configuration introduction

     自動配置是springboot的一大特性,它能在咱們添加相應的jar包依賴的時候,自動爲咱們配置了這些組件的相關配置,咱們無需配置或者只須要少許的配置就能運行咱們編寫的項目。官網也對自動配置做了詳細說明:java

     Spring Boot auto-configuration attempts to automatically configure your Spring application based on the jar dependencies that you have added. For example, if HSQLDB is on your classpath, and you have not manually configured any database connection beans, then Spring Boot auto-configures an in-memory database.web

     上訴語句翻譯爲中文爲:SpringBoot自動配置嘗試根據您添加的JAR依賴性自動配置您的Spring應用程序。例如,若是HSQLDB在您的類路徑上,而且您沒有手動配置任何數據庫鏈接bean,那麼spring boot會自動配置內存中的數據庫。spring

     若是須要設置自動配置的話,方法是將@EnableAutoconfiguration註解添加到您的@configuration類中。數據庫

2、How to Realize Auto-Configuration(底層)

    一、要研究springboot的自動配置須要從主程序類開始入手,請看下面代碼:

@SpringBootApplication
public class MMVirusScanApplication {

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

    二、這就是主程序類,這個類最主要的部分是@SpringBootApplication,正是經過添加了這個註解,springboot應用才能正常啓動。再繼續查看@SpringbootApplication註解組成部分

@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 {}

     這個註解最主要部分是:apache

  • @SpringBootConfiguration:這個註解標註在某類上,說明這個類是一個springboot配置類
  • @EnableAutoConfiguration:這個註解就是springboot能實現自動配置的關鍵
  • @ComponentScan:這個註解是組件掃描這個是咱們最熟悉的註解,即便沒有使用過註解也常常在spring的配置文件中使用過<context:component-scan base-package="com.xxx.xxx"/>, 組件掃描就是掃描指定的包下的類,並加載符合條件的組件。

     三、繼續研究@EnableAutoConfiguration註解:

@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 {};
}

     能夠發現它是一個組合註解,Spring 中有不少以Enable開頭的註解,其做用就是藉助@Import來收集並註冊特定場景相關的bean,並加載到IoC容器。@EnableAutoConfiguration就是藉助@Import來收集全部符合自動配置條件的bean定義,並加載到IoC容器。裏面最主要註解是:springboot

  • @AutoConfigurationPackage:自動配置包,它也是一個組合註解,其中最重要的註解是@Import(AutoConfigurationPackages.Registrar.class),它是spring框架的底層註解,它的做用就是給容器中導入某個組件類,例如@Import(AutoConfigurationPackages.Registrar.class),它就是將Registrar這個組件類導入到容器中,可查看Registrar類中registerBeanDefinitions方法,這個方法就是導入組件類的具體實現。
@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata,
				BeanDefinitionRegistry registry) {
             //將註解標註的元信息傳入,獲取到相應的包名
			register(registry, new PackageImport(metadata).getPackageName());
		}

    經過對registerBeanDefinitions方法進行DeBug,運行結果以下:併發

 

     能夠看到AnnotationMetadata(註解標註注的元信息中包含了使用了哪些註解,相應的註解做用在哪一個類上)app

     咱們對new PackageImport(metadata).getPackageName()進行檢索(idea工具能夠圈出須要查詢的值,使用快捷鍵「Ctrl+U」),看看其結果是什麼?框架

   

 

   所以能夠得知使用@AutoConfigurationPackage註解就是將主程序類所在包及全部子包下的組件到掃描到spring容器中socket

  • @Import({AutoConfigurationImportSelector.class}):AutoConfigurationImportSelector這個類導入到spring容器中,AutoConfigurationImportSelector能夠幫助springboot應用將全部符合條件的@Configuration配置都加載到當前SpringBoot建立並使用的IoC容器(ApplicationContext)中。

    四、繼續研究AutoConfigurationImportSelector這個類,經過源碼分析這個類中是經過selectImports這個方法告訴springboot都須要導入那些組件:

@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());
	}

     4.一、深刻研究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.5.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);
		}
	}

     4.二、深刻研究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);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
		configurations = filter(configurations, autoConfigurationMetadata);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

      4.2.一、深刻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;
	}

    /**
	 * Return the class used by {@link SpringFactoriesLoader} to load configuration
	 * candidates.
	 * @return the factory class
	 */
	protected Class<?> getSpringFactoriesLoaderFactoryClass() {
		return EnableAutoConfiguration.class;
	}

    
    protected ClassLoader getBeanClassLoader() {
		return this.beanClassLoader;
	}

   繼續點開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;

  五、總結

     所以springboot底層實現自動配置的步驟是:

  1. springboot應用啓動;
  2. @SpringBootApplication起做用;
  3. @EnableAutoConfiguration;
  4. @AutoConfigurationPackage:這個組合註解主要是@Import(AutoConfigurationPackages.Registrar.class),它經過將Registrar類導入到容器中,而Registrar類做用是掃描主配置類同級目錄以及子包,並將相應的組件導入到springboot建立管理的容器中;
  5. @Import(AutoConfigurationImportSelector.class):它經過將AutoConfigurationImportSelector類導入到容器中,AutoConfigurationImportSelector類做用是經過selectImports方法實現將配置類信息交給SpringFactory加載器進行一系列的容器建立過程,具體實現可查看上面貼附的源碼。

3、Custom Project to Realize Auto-Configuration

     若是突發奇想恰好項目需求須要使用自動配置做一些操做,那麼接下來的環節將對你特別有幫助,精彩環節請不要離開。

     一、new建立maven項目

       1.一、我這裏以IntelliJ IDEA建立Project爲例,其實很簡單,建立一個Maven項目,可是注意建立的時候選擇quickstart,步驟以下:

       

       1.二、點擊next,輸入GroupId和ArtifactId,繼續字母都爲小寫,通常以「com」開頭:    

            

      找到next繼續下一步。

      1.三、配置好maven相關配置

      1.四、輸入project name和設置好項目存放位置點擊finish便可。

       二、引入依賴

       建立完項目後在pom文件中引入spring-boot-autoconfigure 和httpclient的依賴,具體pom文件以下所示:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.demo</groupId>
  <artifactId>customproject</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>           <!--記住打包方式爲jar-->

  <name>customproject Maven Webapp</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>1.8</java.version>
    <maven.compiler.source>${java.version}</maven.compiler.source>
    <maven.compiler.target>${java.version}</maven.compiler.target>
  </properties>

  <dependencies>
        <!--引入httpclient依賴-->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.6</version>
        </dependency>

        <!--引入spring-boot-autoconfigure依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
            <version>2.1.5.RELEASE</version>
        </dependency>

        <!--lombok依賴 簡化實體類,封裝了set和get等方法-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.18</version>
        </dependency>
  </dependencies>

  <!--添加此配置能在maven使用install等命令時將resources目錄下文件都引入-->
  <build>
    <finalName>customproject</finalName>
    <resources>
      <resource>
        <directory>src/main/java</directory>
        <includes>
          <include>**/*.xml</include>
        </includes>
      </resource>
      <resource>
        <directory>src/main/resources</directory>
        <includes>
          <include>**/*</include>
        </includes>
      </resource>
    </resources>
  </build>
</project>

      三、HttpClient 配置信息的配置類 HttpClientProperties

@Data
@ConfigurationProperties(prefix = "spring.httpclient")
class HttpClientProperties {
    private Integer connectTimeout = 1000;//建立鏈接的最長時間
    private Integer socketTimeout = 10000;//數據傳輸的最長時間
    private String agent = "agent";
    private Integer maxPerRoute = 10;//設置每一個路由的併發數
    private Integer maxTotal = 50;//最大鏈接數

}
@ConfigurationProperties註解做用:讀取application.properties文件中的內容,根據配置的prefix屬性,將prefix屬性 
對應的內容生成鍵值對複製給使用了@ConfigurationProperties註解類中

     當咱們建立好配置信息類以後,使用@ConfigurationProperties註解是會出錯,出錯內容如圖所示:

     

     緣由是咱們沒有使用到@EnableConfigurationProperties註解,當咱們使用了以後就不會出錯了。

      四、配置自動配置類

@Configuration
@ConditionalOnClass({HttpClient.class})
@EnableConfigurationProperties(HttpClientProperties.class)
public class HttpClientAutoConfiguration {

    private  HttpClientProperties httpClientProperties;

    public HttpClientAutoConfiguration(HttpClientProperties httpClientProperties) {
        this.httpClientProperties = httpClientProperties;
    }
    @Bean
    @ConditionalOnMissingBean(HttpClient.class)
    public HttpClient httpClient() {
        RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(httpClientProperties.getConnectTimeout())
                .setSocketTimeout(httpClientProperties.getSocketTimeout()).build();

        HttpClient httpClient = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig)
                .setUserAgent(httpClientProperties.getAgent()).setMaxConnPerRoute(httpClientProperties.getMaxPerRoute())
                .setConnectionReuseStrategy(new NoConnectionReuseStrategy()).build();
        return httpClient;
    }
}

@Configuration:標註該類是一個配置類
@Bean:至關於原始的在xml文件中配置<bean id="">,聲明在方法上用於將實例對象注入Spring上下文中。
@ConditionalOnClass:該註解的參數對應的類必須存在,不然不解析該註解修飾的配置類;
@ConditionalOnMissingBean:該註解表示,若是存在它修飾的類的bean,則不須要再建立這個bean;能夠給該註解傳入參數例如@ConditionOnMissingBean(name = 「example」),這個表示若是name爲「example」的bean存在,這該註解修飾的代碼塊不執行。
@EnableConfigurationProperties:會將會HttpClientProperties 做爲一個Bean引入HttpClientAutoConfiguration 中。

      五、註冊配置

         正常狀況下咱們按步驟一建立出來的Project是沒有resources這個文件夾的,在IntelliJ IDEA這個工具中,咱們須要先建立一個directory,而後將之設置爲resources root便可,設置方式以下:選中resources目錄右鍵單擊–》Mark Directory as–》Resource Root。

         在resources目錄下新建META-INF目錄,而後在META-INF目錄下建立spring.factories文件,文件內容以下,表示設置自動配置類的位置,如有多個配置類用」,」隔開便可。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.lijunkui.autoconfig.HttpClientAutoConfiguration    #此處記得必定要配置HttpClientAutoConfiguration類所在包路徑

       六、項目中使用

          使用maven的install安裝到本地倉庫後,在建立好springboot以後,添加上依賴,就能夠很方便的使用了。

相關文章
相關標籤/搜索