SpringBoot源码深度解析(七)springBoot 依赖管理及自动配置

15 篇文章 0 订阅
9 篇文章 4 订阅

SpringBoot 源码深度解析及自动配置

第六章 springBoot 依赖管理


前言


一、为什么导入dependency时不需要指定版本?

打开pom.xml文件:
在这里插入图片描述

<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.2.9.RELEASE</version>
	<relativePath/> <!-- lookup parent from repository -->
</parent>

点击artifactId进入 spring-boot-starter-parent

<?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>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.2.9.RELEASE</version>
    <relativePath>../../spring-boot-dependencies</relativePath>
  </parent>
  <artifactId>spring-boot-starter-parent</artifactId>
  <packaging>pom</packaging>
  <name>Spring Boot Starter Parent</name>
  <description>Parent pom providing dependency and plugin management for applications
		built with Maven</description>
  <url>https://projects.spring.io/spring-boot/#/spring-boot-starter-parent</url>
  <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>
  <build>
    <resources>
      <resource>
        <filtering>true</filtering>
        <directory>${basedir}/src/main/resources</directory>
        <includes>
          <include>**/application*.yml</include>
          <include>**/application*.yaml</include>
          <include>**/application*.properties</include>
        </includes>
      </resource>
      <resource>
        <directory>${basedir}/src/main/resources</directory>
        <excludes>
          <exclude>**/application*.yml</exclude>
          <exclude>**/application*.yaml</exclude>
          <exclude>**/application*.properties</exclude>
        </excludes>
      </resource>
    </resources>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.jetbrains.kotlin</groupId>
          <artifactId>kotlin-maven-plugin</artifactId>
          <version>${kotlin.version}</version>
          <executions>
            <execution>
              <id>compile</id>
              <phase>compile</phase>
              <goals>
                <goal>compile</goal>
              </goals>
            </execution>
            <execution>
              <id>test-compile</id>
              <phase>test-compile</phase>
              <goals>
                <goal>test-compile</goal>
              </goals>
            </execution>
          </executions>
          <configuration>
            <jvmTarget>${java.version}</jvmTarget>
            <javaParameters>true</javaParameters>
          </configuration>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <configuration>
            <parameters>true</parameters>
          </configuration>
        </plugin>
        <plugin>
          <artifactId>maven-failsafe-plugin</artifactId>
          <executions>
            <execution>
              <goals>
                <goal>integration-test</goal>
                <goal>verify</goal>
              </goals>
            </execution>
          </executions>
          <configuration>
            <classesDirectory>${project.build.outputDirectory}</classesDirectory>
          </configuration>
        </plugin>
        <plugin>
          <artifactId>maven-jar-plugin</artifactId>
          <configuration>
            <archive>
              <manifest>
                <mainClass>${start-class}</mainClass>
                <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
              </manifest>
            </archive>
          </configuration>
        </plugin>
        <plugin>
          <artifactId>maven-war-plugin</artifactId>
          <configuration>
            <archive>
              <manifest>
                <mainClass>${start-class}</mainClass>
                <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
              </manifest>
            </archive>
          </configuration>
        </plugin>
        <plugin>
          <groupId>org.codehaus.mojo</groupId>
          <artifactId>exec-maven-plugin</artifactId>
          <configuration>
            <mainClass>${start-class}</mainClass>
          </configuration>
        </plugin>
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <configuration>
            <delimiters>
              <delimiter>${resource.delimiter}</delimiter>
            </delimiters>
            <useDefaultDelimiters>false</useDefaultDelimiters>
          </configuration>
        </plugin>
        <plugin>
          <groupId>pl.project13.maven</groupId>
          <artifactId>git-commit-id-plugin</artifactId>
          <executions>
            <execution>
              <goals>
                <goal>revision</goal>
              </goals>
            </execution>
          </executions>
          <configuration>
            <verbose>true</verbose>
            <dateFormat>yyyy-MM-dd'T'HH:mm:ssZ</dateFormat>
            <generateGitPropertiesFile>true</generateGitPropertiesFile>
            <generateGitPropertiesFilename>${project.build.outputDirectory}/git.properties</generateGitPropertiesFilename>
          </configuration>
        </plugin>
        <plugin>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-maven-plugin</artifactId>
          <executions>
            <execution>
              <id>repackage</id>
              <goals>
                <goal>repackage</goal>
              </goals>
            </execution>
          </executions>
          <configuration>
            <mainClass>${start-class}</mainClass>
          </configuration>
        </plugin>
        <plugin>
          <artifactId>maven-shade-plugin</artifactId>
          <executions>
            <execution>
              <phase>package</phase>
              <goals>
                <goal>shade</goal>
              </goals>
              <configuration>
                <transformers>
                  <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                    <resource>META-INF/spring.handlers</resource>
                  </transformer>
                  <transformer implementation="org.springframework.boot.maven.PropertiesMergingResourceTransformer">
                    <resource>META-INF/spring.factories</resource>
                  </transformer>
                  <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                    <resource>META-INF/spring.schemas</resource>
                  </transformer>
                  <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
                  <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                    <mainClass>${start-class}</mainClass>
                  </transformer>
                </transformers>
              </configuration>
            </execution>
          </executions>
          <dependencies>
            <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-maven-plugin</artifactId>
              <version>2.2.9.RELEASE</version>
            </dependency>
          </dependencies>
          <configuration>
            <keepDependenciesWithProvidedScope>true</keepDependenciesWithProvidedScope>
            <createDependencyReducedPom>true</createDependencyReducedPom>
            <filters>
              <filter>
                <artifact>*:*</artifact>
                <excludes>
                  <exclude>META-INF/*.SF</exclude>
                  <exclude>META-INF/*.DSA</exclude>
                  <exclude>META-INF/*.RSA</exclude>
                </excludes>
              </filter>
            </filters>
          </configuration>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

来解释一下这个文件:

<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>

该部分进行了一些属性的声明,其中<resource.delimiter>@</resource.delimiter>是设置了一个与${}不冲突的一个分隔符。

<resources>
  <resource>
    <filtering>true</filtering>
    <directory>${basedir}/src/main/resources</directory>
    <includes>
      <include>**/application*.yml</include>
      <include>**/application*.yaml</include>
      <include>**/application*.properties</include>
    </includes>
  </resource>
  <resource>
    <directory>${basedir}/src/main/resources</directory>
    <excludes>
      <exclude>**/application*.yml</exclude>
      <exclude>**/application*.yaml</exclude>
      <exclude>**/application*.properties</exclude>
    </excludes>
  </resource>
</resources>

这段代码的意思是:为当前应用程序开启了一些属性过滤

<pluginManagement>
  <plugins>
    <plugin>
      <groupId>org.jetbrains.kotlin</groupId>
      <artifactId>kotlin-maven-plugin</artifactId>
      <version>${kotlin.version}</version>
      <executions>
        <execution>
          <id>compile</id>
          <phase>compile</phase>
          <goals>
            <goal>compile</goal>
          </goals>
        </execution>
        <execution>
          <id>test-compile</id>
          <phase>test-compile</phase>
          <goals>
            <goal>test-compile</goal>
          </goals>
        </execution>
      </executions>
      <configuration>
        <jvmTarget>${java.version}</jvmTarget>
        <javaParameters>true</javaParameters>
      </configuration>
    </plugin>
    <plugin>
      <artifactId>maven-compiler-plugin</artifactId>
      <configuration>
        <parameters>true</parameters>
      </configuration>
    </plugin>
    <plugin>
      <artifactId>maven-failsafe-plugin</artifactId>
      <executions>
        <execution>
          <goals>
            <goal>integration-test</goal>
            <goal>verify</goal>
          </goals>
        </execution>
      </executions>
      <configuration>
        <classesDirectory>${project.build.outputDirectory}</classesDirectory>
      </configuration>
    </plugin>
    <plugin>
      <artifactId>maven-jar-plugin</artifactId>
      <configuration>
        <archive>
          <manifest>
            <mainClass>${start-class}</mainClass>
            <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
          </manifest>
        </archive>
      </configuration>
    </plugin>
    <plugin>
      <artifactId>maven-war-plugin</artifactId>
      <configuration>
        <archive>
          <manifest>
            <mainClass>${start-class}</mainClass>
            <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
          </manifest>
        </archive>
      </configuration>
    </plugin>
    <plugin>
      <groupId>org.codehaus.mojo</groupId>
      <artifactId>exec-maven-plugin</artifactId>
      <configuration>
        <mainClass>${start-class}</mainClass>
      </configuration>
    </plugin>
    <plugin>
      <artifactId>maven-resources-plugin</artifactId>
      <configuration>
        <delimiters>
          <delimiter>${resource.delimiter}</delimiter>
        </delimiters>
        <useDefaultDelimiters>false</useDefaultDelimiters>
      </configuration>
    </plugin>
    <plugin>
      <groupId>pl.project13.maven</groupId>
      <artifactId>git-commit-id-plugin</artifactId>
      <executions>
        <execution>
          <goals>
            <goal>revision</goal>
          </goals>
        </execution>
      </executions>
      <configuration>
        <verbose>true</verbose>
        <dateFormat>yyyy-MM-dd'T'HH:mm:ssZ</dateFormat>
        <generateGitPropertiesFile>true</generateGitPropertiesFile>
        <generateGitPropertiesFilename>${project.build.outputDirectory}/git.properties</generateGitPropertiesFilename>
      </configuration>
    </plugin>
    <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
      <executions>
        <execution>
          <id>repackage</id>
          <goals>
            <goal>repackage</goal>
          </goals>
        </execution>
      </executions>
      <configuration>
        <mainClass>${start-class}</mainClass>
      </configuration>
    </plugin>
    <plugin>
      <artifactId>maven-shade-plugin</artifactId>
      <executions>
        <execution>
          <phase>package</phase>
          <goals>
            <goal>shade</goal>
          </goals>
          <configuration>
            <transformers>
              <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                <resource>META-INF/spring.handlers</resource>
              </transformer>
              <transformer implementation="org.springframework.boot.maven.PropertiesMergingResourceTransformer">
                <resource>META-INF/spring.factories</resource>
              </transformer>
              <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                <resource>META-INF/spring.schemas</resource>
              </transformer>
              <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
              <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                <mainClass>${start-class}</mainClass>
              </transformer>
            </transformers>
          </configuration>
        </execution>
      </executions>
      <dependencies>
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-maven-plugin</artifactId>
          <version>2.2.9.RELEASE</version>
        </dependency>
      </dependencies>
      <configuration>
        <keepDependenciesWithProvidedScope>true</keepDependenciesWithProvidedScope>
        <createDependencyReducedPom>true</createDependencyReducedPom>
        <filters>
          <filter>
            <artifact>*:*</artifact>
            <excludes>
              <exclude>META-INF/*.SF</exclude>
              <exclude>META-INF/*.DSA</exclude>
              <exclude>META-INF/*.RSA</exclude>
            </excludes>
          </filter>
        </filters>
      </configuration>
    </plugin>
  </plugins>
</pluginManagement>

这一段代码就是springBoot的插件管理,提供了一些插件的默认值;
我们接下来继续进入

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

点击进入spring-boot-dependencies,解决问题;

二、

查看spring-boot-starter-web依赖文件源码,核心代码具体如下:

<dependencies>
	<dependency>
	  <groupId>org.springframework.boot</groupId>
	  <artifactId>spring-boot-starter-tomcat</artifactId> 	
	</dependency>
	<dependency>
	  <groupId>org.springframework.boot</groupId>
	  <artifactId>spring-boot-starter-validation</artifactId>
	  <exclusions> <exclusion> 
	  <groupId>org.apache.tomcat.embed</groupId> 
	  <artifactId>tomcat-embed-el</artifactId>
	  </exclusion> </exclusions>
	</dependency>
	<dependency>
	  <groupId>org.springframework</groupId>
	  <artifactId>spring-web</artifactId>
	</dependency> 
	<dependency> 
	 <groupId>org.springframework</groupId> 
	 <artifactId>spring-webmvc</artifactId> 
	</dependency>
</dependencies>

从上述代码可以发现,spring-boot-starter-web依赖启动器的主要作用是打包了Web开发场景所需的底层所有依赖(基于依赖传递,当前项目也存在对应的依赖jar包)
正是如此,在pom.xml中引入spring-boot-starter-web依赖启动器时,就可以实现Web场景开发,而不需要额外导入Tomcat服务器以及其他Web依赖文件等。
当然,这些引入的依赖文件的版本号还是由spring-boot-starter-parent父依赖进行的统一管理。

三、@SpringBootConfiguration分析

Spring Boot应用的启动入口是@SpringBootApplication注解标注类中的main()方法, @SpringBootApplicationSpringBoot 应用标注在某个类上说明这个类是 SpringBoot 的主配置类, SpringBoot 就应该运行这个类的 main() 方法启动 SpringBoot 应用。
点击进入@SpringBootConfiguration注解:

@Target(ElementType.TYPE) //注解的适用范围,Type表示注解可以描述在类、接口、注解或枚举中
@Retention(RetentionPolicy.RUNTIME) //表示注解的生命周期,Runtime运行时
@Documented //表示注解可以记录在javadoc中
@Inherited //表示可以被子类继承该注解
//------------------------------------------------
@SpringBootConfiguration  // 标明该类为配置类
@EnableAutoConfiguration  // 启动自动配置功能
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) //注解扫描
public @interface SpringBootApplication {

	/**
	 * Exclude specific auto-configuration classes such that th
	 * ey will never be applied.
	 * @return the classes to exclude
	 */
	// 根据class来排除特定的类,使其不能加入spring容器,传入参数value类型是class类型。
	@AliasFor(annotation = EnableAutoConfiguration.class)
	Class<?>[] exclude() default {};

	/**
	 * Exclude specific auto-configuration class names such that they will never be
	 * applied.
	 * @return the class names to exclude
	 * @since 1.3.0
	 */
	// 根据classname 来排除特定的类,使其不能加入spring容器,传入参数value类型是class的全类名字符串数组
	@AliasFor(annotation = EnableAutoConfiguration.class)
	String[] excludeName() default {};

	/**
	 * Base packages to scan for annotated components. Use {@link #scanBasePackageClasses}
	 * for a type-safe alternative to String-based package names.
	 * <p>
	 * <strong>Note:</strong> this setting is an alias for
	 * {@link ComponentScan @ComponentScan} only. It has no effect on {@code @Entity}
	 * scanning or Spring Data {@link Repository} scanning. For those you should add
	 * {@link org.springframework.boot.autoconfigure.domain.EntityScan @EntityScan} and
	 * {@code @Enable...Repositories} annotations.
	 * @return base packages to scan
	 * @since 1.3.0
	 */
	// 指定扫描包,参数是包名的字符串数组。
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
	String[] scanBasePackages() default {};

	/**
	 * Type-safe alternative to {@link #scanBasePackages} for specifying the packages to
	 * scan for annotated components. The package of each class specified will be scanned.
	 * <p>
	 * Consider creating a special no-op marker class or interface in each package that
	 * serves no purpose other than being referenced by this attribute.
	 * <p>
	 * <strong>Note:</strong> this setting is an alias for
	 * {@link ComponentScan @ComponentScan} only. It has no effect on {@code @Entity}
	 * scanning or Spring Data {@link Repository} scanning. For those you should add
	 * {@link org.springframework.boot.autoconfigure.domain.EntityScan @EntityScan} and
	 * {@code @Enable...Repositories} annotations.
	 * @return base packages to scan
	 * @since 1.3.0
	 */
	// 扫描特定的包,参数类似是Class类型数组。
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
	Class<?>[] scanBasePackageClasses() default {};

	/**
	 * Specify whether {@link Bean @Bean} methods should get proxied in order to enforce
	 * bean lifecycle behavior, e.g. to return shared singleton bean instances even in
	 * case of direct {@code @Bean} method calls in user code. This feature requires
	 * method interception, implemented through a runtime-generated CGLIB subclass which
	 * comes with limitations such as the configuration class and its methods not being
	 * allowed to declare {@code final}.
	 * <p>
	 * The default is {@code true}, allowing for 'inter-bean references' within the
	 * configuration class as well as for external calls to this configuration's
	 * {@code @Bean} methods, e.g. from another configuration class. If this is not needed
	 * since each of this particular configuration's {@code @Bean} methods is
	 * self-contained and designed as a plain factory method for container use, switch
	 * this flag to {@code false} in order to avoid CGLIB subclass processing.
	 * <p>
	 * Turning off bean method interception effectively processes {@code @Bean} methods
	 * individually like when declared on non-{@code @Configuration} classes, a.k.a.
	 * "@Bean Lite Mode" (see {@link Bean @Bean's javadoc}). It is therefore behaviorally
	 * equivalent to removing the {@code @Configuration} stereotype.
	 * @since 2.2
	 * @return whether to proxy {@code @Bean} methods
	 */
	@AliasFor(annotation = Configuration.class)
	boolean proxyBeanMethods() default true;

}

我们主要关注@EnableAutoConfiguration 注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited

@AutoConfigurationPackage // 自动配置包
@Import(AutoConfigurationImportSelector.class) // Spring的底层注解@Import,给容器中导入一个组件;
public @interface EnableAutoConfiguration {

	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	/**
	 * Exclude specific auto-configuration classes such that they will never be applied.
	 * @return the classes to exclude
	 */
	Class<?>[] exclude() default {};

	/**
	 * Exclude specific auto-configuration class names such that they will never be
	 * applied.
	 * @return the class names to exclude
	 * @since 1.3.0
	 */
	String[] excludeName() default {};

}

spring 中有很多以Enable开头的注解,其作用就是借助@Import注解来收集并注册相关场景的bean,并加载到IOC容器当中。
@EnableAutoConfiguration注解就是借助@Import注解来收集所有符合自动配置条件的bean定义,并加载到IOC容器当中
点击进入@AutoConfigurationPackage :

/**
 * Indicates that the package containing the annotated class should be registered with
 * {@link AutoConfigurationPackages}.
 *
 * @author Phillip Webb
 * @since 1.3.0
 * @see AutoConfigurationPackages
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited

	// Spring的底层注解@Import,给容器中导入一个组件;
	// 导入的组件是AutoConfigurationPackages.Registrar.class
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

进入Registrar.class

/**
	 * {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
	 * configuration.
	 */
	static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
			// 将注解标注的元信息传入,获取到相应的包名
			register(registry, new PackageImport(metadata).getPackageName());
		}

		@Override
		public Set<Object> determineImports(AnnotationMetadata metadata) {
			return Collections.singleton(new PackageImport(metadata));
		}

	}

进入register(registry, new PackageImport(metadata).getPackageName());方法

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
		// 这里参数 packageNames 缺省情况下就是一个字符串,是使用了注解
		// @SpringBootApplication 的 Spring Boot 应用程序入口类所在的包

		if (registry.containsBeanDefinition(BEAN)) {
			// 如果该bean已经注册,则将要注册包名称添加进去
			BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
			ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
			constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
		}
		else {
			//如果该bean尚未注册,则注册该bean,参数中提供的包名称会被设置到bean定义中去
			GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
			beanDefinition.setBeanClass(BasePackages.class);
			beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
			beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
			registry.registerBeanDefinition(BEAN, beanDefinition);
		}
	}

AutoConfigurationPackages.Registrar这个类就干一个事,注册一个 Bean ,这个 Bean 就是org.springframework.boot.autoconfigure.AutoConfigurationPackages.BasePackages ,它有一个参数,这个参数是使用了 @AutoConfigurationPackage 这个注解的类所在的包路径,保存自动配置类以供之后的使用,比如给 JPA entity 扫描器用来扫描开发人员通过注解 @Entity 定义的 entity类。

接下来来分析一下@Import(AutoConfigurationImportSelector.class)注解:
@Import({AutoConfigurationImportSelector.class}) :将AutoConfigurationImportSelector 这个类导入到 Spring 容器中,AutoConfigurationImportSelector 可以帮助 Springboot 应用将所有符合条件的 @Configuration配置都加载到当前 SpringBoot 创建并使用的 IOC 容器(ApplicationContext )中。
在这里插入图片描述
可以看到 AutoConfigurationImportSelector 重点是实现了DeferredImportSelector 接口和各种Aware 接口,然后DeferredImportSelector 接口又继承了 ImportSelectorspring导入外部配置的核心接口) 接口。其不光实现了 ImportSelector 接口,还实现了很多其它的 Aware 接口,分别表示在某个时机会被回调;
进入AutoConfigurationImportSelector源码:

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

	private ConfigurableListableBeanFactory beanFactory;

	private Environment environment;

	private ClassLoader beanClassLoader;

	private ResourceLoader resourceLoader;
	...

我们可以看到AutoConfigurationImportSelector类实现了BeanClassLoaderAware,ResourceLoaderAware, BeanFactoryAware, EnvironmentAware四个Aware接口,在实现这些Aware接口并执行回调时会获取到相应的对象,赋值给这几个属性;

确定自动配置实现逻辑的入口方法:
跟自动配置逻辑相关的入口方法在 DeferredImportSelectorGrouping 类的 getImports 方法处,因此我们就从 DeferredImportSelectorGrouping 类的 getImports 方法来开始分析SpringBoot的自动配置源码好了。
先看一下 getImports 方法代码:

private static class DeferredImportSelectorGrouping {
    private final Group group;
    private final List<ConfigurationClassParser.DeferredImportSelectorHolder> deferredImports = new ArrayList();

    DeferredImportSelectorGrouping(Group group) {
        this.group = group;
    }

    public void add(ConfigurationClassParser.DeferredImportSelectorHolder deferredImport) {
        this.deferredImports.add(deferredImport);
    }

    public Iterable<Entry> getImports() {
        Iterator var1 = this.deferredImports.iterator();

        while(var1.hasNext()) {
            ConfigurationClassParser.DeferredImportSelectorHolder deferredImport = (ConfigurationClassParser.DeferredImportSelectorHolder)var1.next();
            this.group.process(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getImportSelector());
        }

        return this.group.selectImports();
    }

    public Predicate<String> getCandidateFilter() {
        Predicate<String> mergedFilter = ConfigurationClassParser.DEFAULT_EXCLUSION_FILTER;
        Iterator var2 = this.deferredImports.iterator();

        while(var2.hasNext()) {
            ConfigurationClassParser.DeferredImportSelectorHolder deferredImport = (ConfigurationClassParser.DeferredImportSelectorHolder)var2.next();
            Predicate<String> selectorFilter = deferredImport.getImportSelector().getExclusionFilter();
            if (selectorFilter != null) {
                mergedFilter = mergedFilter.or(selectorFilter);
            }
        }

        return mergedFilter;
    }
}

this.group.process(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getImportSelector());主要做的事情就是在 this.groupAutoConfigurationGroup 对象的 process 方法中,传入的AutoConfigurationImportSelector对象来选择一些符合条件的自动配置类,过滤掉一些不符合条件的自动配置类,就是这么个事情。
继续向下,点击进入this.group.process(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getImportSelector());方法:

// 这里用来处理自动配置类,比如过滤掉不符合匹配条件的自动配置类
@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
	Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
			() -> String.format("Only %s implementations are supported, got %s",
					AutoConfigurationImportSelector.class.getSimpleName(),
					deferredImportSelector.getClass().getName()));

	// 【1】,调用getAutoConfigurationEntry方法得到自动配置类放入autoConfigurationEntry对象中
	AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
			.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);

	// 【2】,又将封装了自动配置类的autoConfigurationEntry对象装进autoConfigurationEntries集合
	this.autoConfigurationEntries.add(autoConfigurationEntry);
	// 【3】,遍历刚获取的自动配置类
	for (String importClassName : autoConfigurationEntry.getConfigurations()) {
		// 这里符合条件的自动配置类作为key,annotationMetadata作为值放进entries集合
		this.entries.putIfAbsent(importClassName, annotationMetadata);
	}
}

点击进入getAutoConfigurationEntry方法:

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
		AnnotationMetadata annotationMetadata) {
	// 获取是否有配置spring.boot.enableautoconfiguration属性,默认返回true
	if (!isEnabled(annotationMetadata)) {
		return EMPTY_ENTRY;
	}
	AnnotationAttributes attributes = getAttributes(annotationMetadata);

	// 【1】得到spring.factories文件配置的所有自动配置类
	List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);

	// 利用LinkedHashSet移除重复的配置类
	configurations = removeDuplicates(configurations);

	// 得到要排除的自动配置类,比如注解属性exclude的配置类
	// 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
	// 将会获取到exclude = FreeMarkerAutoConfiguration.class的注解数据
	Set<String> exclusions = getExclusions(annotationMetadata, attributes);

	// 检查要被排除的配置类,因为有些不是自动配置类,故要抛出异常
	checkExcludedClasses(configurations, exclusions);

	// 【2】将要排除的配置类移除
	configurations.removeAll(exclusions);

	// 【3】因为从spring.factories文件获取的自动配置类太多,如果有些不必要的自动配置类都加载进内存,会造成内存浪费,因此这里需要进行过滤
	configurations = filter(configurations, autoConfigurationMetadata);

	// 【4】获取了符合条件的自动配置类后,此时触发AutoConfigurationImportEvent事件,
	// 目的是告诉ConditionEvaluationReport条件评估报告器对象来记录符合条件的自动配置类
	fireAutoConfigurationImportEvents(configurations, exclusions);

	// 【5】将符合条件和要排除的自动配置类封装进AutoConfigurationEntry对象,并返回
	return new AutoConfigurationEntry(configurations, exclusions);
}

可以看到:

// 【1】得到spring.factories文件配置的所有自动配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);

这个方法主要就是用于加载配置文件,把在springboot启动过程中涉及到的所有可以进行自动配置的自动配置类,点击进入这个方法:

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

继续向下,进入:

   public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        ClassLoader classLoaderToUse = classLoader;
        if (classLoader == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }

        String factoryTypeName = factoryType.getName();
        return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
    }

  private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
        Map<String, List<String>> result = (Map)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            HashMap result = new HashMap();

            try {
                Enumeration urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);

                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 factoryTypeName = ((String)entry.getKey()).trim();
                        String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        String[] var10 = factoryImplementationNames;
                        int var11 = factoryImplementationNames.length;

                        for(int var12 = 0; var12 < var11; ++var12) {
                            String factoryImplementationName = var10[var12];
                            ((List)result.computeIfAbsent(factoryTypeName, (key) -> {
                                return new ArrayList();
                            })).add(factoryImplementationName.trim());
                        }
                    }
                }

                result.replaceAll((factoryType, implementations) -> {
                    return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
                });
                cache.put(classLoader, result);
                return result;
            } catch (IOException var14) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
            }
        }
    }

我们主要关注:

FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"

从代码中我们可以知道,在这个方法中会遍历整个ClassLoader中所有jar包下的spring.factories文件。
spring.factories里面保存着springboot的默认提供的自动配置类。

META-INF/spring.factories文件:
在这里插入图片描述
所以我们可以知道loadFactoryNames这个方法返回的就是上面org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的所有内容。
在拿到所有的可装配类之后,springBoot根据需要去加载需要装配的类;

// 利用LinkedHashSet移除重复的配置类
	configurations = removeDuplicates(configurations);

	// 得到要排除的自动配置类,比如注解属性exclude的配置类
	// 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
	// 将会获取到exclude = FreeMarkerAutoConfiguration.class的注解数据
	Set<String> exclusions = getExclusions(annotationMetadata, attributes);

	// 检查要被排除的配置类,因为有些不是自动配置类,故要抛出异常
	checkExcludedClasses(configurations, exclusions);

	// 【2】将要排除的配置类移除
	configurations.removeAll(exclusions);

	// 【3】因为从spring.factories文件获取的自动配置类太多,如果有些不必要的自动配置类都加载进内存,会造成内存浪费,因此这里需要进行过滤
	configurations = filter(configurations, autoConfigurationMetadata);

	// 【4】获取了符合条件的自动配置类后,此时触发AutoConfigurationImportEvent事件,
	// 目的是告诉ConditionEvaluationReport条件评估报告器对象来记录符合条件的自动配置类
	fireAutoConfigurationImportEvents(configurations, exclusions);

	// 【5】将符合条件和要排除的自动配置类封装进AutoConfigurationEntry对象,并返回
	return new AutoConfigurationEntry(configurations, exclusions);
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值