Spring Boot 自动配置原理解析

Spring Boot 使得创建可以“直接运行”的独立的、生产级的基于 Spring 的应用程序变得很容易。当我们直接使用 Spring Framework 集成第三方框架时,使用过的朋友都知道需要繁琐的配置。如果我们使用 Spring Boot 时,如果需要集成缓存框架 Redis 时,只需要在开发的项目中引入 Redis 相关的配置就可以 RedisTempalte 操作本地早已启动好的 Redis 服务。

Redis 依赖

 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-redis</artifactId>
     <version>${
     spring.boot-version}</version>
 </dependency>

那么 Spring Boot 是如何做到的呢?下面我们就来揭开它神秘的面纱。

1、一个简单的 Spring Boot 项目

下面我们就基于 Maven 作项目管理使用 Spring Boot 开发一个简单的 Web 项目。

1.1 pom.xml

pom.xml 添加 spring-boot-starter-web 依赖 jar 包,就可以开发 web 项目了。

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.8.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>cn.carlzone.springboot</groupId>
    <artifactId>springboot-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
    </properties>

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

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

1.2 Spring Boot 启动类

写一个类 SpringbootDemoApplication,并使用 @SpringBootApplication 标注这个类,然后写一个 main 方法并使用 SpringApplication#run 来启动这个类。这就是一个简单的 Spring Boot 项目。

我们在这个类上添加 Spring Mvc 声明 Restful 的注解 @RestController,并且添加一个返回字符串 index请求映射。

SpringbootDemoApplication.java

@RestController
@SpringBootApplication
public class SpringbootDemoApplication {
     

    @RequestMapping("/")
    public String index(){
     
        return "index";
    }

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

}

1.3 访问请求

在浏览器访问 http:localhost:8080 ,就会显示 index
Spring Boot 自动配置原理解析_第1张图片
这样一个简单的 web 项目就完成了, 使用 Spring Boot 开发一个 web 项目是不是特别简单。然后如果集成 redis 就只需要添加上面 redis 的依赖就行了。

2、Spring Boot 启动注解解析

从上面的例子我们可以看出 Spring Boot 项目表面上就是一个 main 方法,和其它普通的类唯一特别的地方就是在类上标注了 @SpringBootApplication 注解。并且在 main 方法中通过 SpringApplication#run 启动。首先我们先来分析一下 @SpringBootApplication 注解。

SpringBootApplication.java

@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, attribute = "exclude")
	Class<?>[] exclude() default {
     };

	@AliasFor(annotation = EnableAutoConfiguration.class, attribute = "excludeName")
	String[] excludeName() default {
     };

	@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
	String[] scanBasePackages() default {
     };

	@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
	Class<?>[] scanBasePackageClasses() default {
     };

}

@SpringBootApplication 注解是一个组合注解,那我们就从它包含的注解以及它的方法来分析它。

2.1 包含的注解

@SpringBootApplication 注解是一个组合注解,在它上面包含: @SpringBootConfiguration@EnableAutoConfigurationComponentScan 这三个注解。

  • @SpringBootConfiguration:它其实被 @Configuration 注解标注,直接翻译它就是 Spring Boot 的配置,在 Spring 3.0 之后,因为在 Spring 3.0 之后可以使用 @Configuration@Bean 来声明 Spring Bean。
  • @EnableAutoConfiguration:它包含一个注解 @AutoConfigurationPackage ,它主要的作用就是扫描 Spring Boot 启动类所在的包及其子包下面定义的 Spring Bean。并且 @SpringBootConfiguration 注解通过 @Import 注解添加了EnableAutoConfigurationImportSelector。这个就是 Spring Boot 自动配置的核心处理,下后面的章节我们再来分析它。
  • @ComponentScan:注解的作用就是扫描 Spring 指定包下面的 bean。

所以 @SpringBootApplication 其实就是定义了一个 Spring 的 Bean,并且这个 bean 有自动配置的功能,然后还会扫描启动类所在的包及其子包下面定义的 Spring 的 Bean。

2.2 包含的方法

@SpringBootApplication 注解里面有 4 个方法,这些方法其实是通过 @AliasFor 注解对它上面的 @EnableAutoConfiguration@ComponentScan注解功能的自定义。

  • Class[] exclude() default {}: 自定义 @EnableAutoConfiguration 注解的 exclude 属性,这个属性的作用是以 Class 的形式排除自动依赖注入。举个例子,比如你需要自己自定义数据源,不需要 Spring Boot 数据源的自动配置,就可以排除 DataSourceAutoConfiguration.java
  • String[] excludeName() default {};:自定义 @EnableAutoConfiguration 注解的 excludeName 属性,这个属性的作用是以 ClassName 的形式排除自动依赖注入。举个例子,比如你需要自己自定义数据源,不需要 Spring Boot 数据源的自动配置,就可以排除 org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
  • String[] scanBasePackages() default {}:自定义 @ComponentScan 注解的 basePackages 属性,这个属性的作用是定义 Spring Boot 需要扫描的包,扫描这个包下面的所有 bean。因为 Spring Boot 默认只会扫描启动类所在的包。
  • Class[] scanBasePackageClasses() default {}:自定义 @ComponentScan 注解的 basePackageClasses 属性,这个属性的作用是定义 Spring Boot 需要扫描的类,这个类上面定义了 Spring Boot 的 Class 配置 Bean 的相关信息。

所以 @SpringBootApplication 注解里面有 4 个方法就是自定义自动配置的类,以及 Spring Bean 的定义。

3、EnableAutoConfigurationImportSelector

下面我们回到我们的自动依赖配置, Spring Boot 的自动依赖配置是通过 EnableAutoConfigurationImportSelector。这个类有一个方法就是EnableAutoConfigurationImportSelector#isEnabled,它的作用是判断是否激活自动依赖配置。默认是激活的,可以通过在 Spring Environment 环境变量中添加spring.boot.enableautoconfiguration 属性为 false 来不激活 Spring Boot 的自动依赖配置。

EnableAutoConfigurationImportSelector继承于 AutoConfigurationImportSelector,这个类通过实现 DeferredImportSelector 最终实现了 ImportSelector 接口。这个接口是 Spring 3.1 之后的新接口,主要的作用就是加载标注了@Configuration 的类全名所定义的 Spring Bean。

ImportSelector.java

public interface ImportSelector {

	/**
	 * Select and return the names of which class(es) should be imported based on
	 * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
	 */
	String[] selectImports(AnnotationMetadata importingClassMetadata);

}

下面我们来分析一下 AutoConfigurationImportSelector#selectImports 来看一下 Spring Boot 是如何作自动依赖配置的。

AutoConfigurationImportSelector#selectImports

public String[] selectImports(AnnotationMetadata annotationMetadata) {
	if (!isEnabled(annotationMetadata)) {
		return NO_IMPORTS;
	}
	try {
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		List configurations = getCandidateConfigurations(annotationMetadata,
				attributes);
		configurations = removeDuplicates(configurations);
		configurations = sort(configurations, autoConfigurationMetadata);
		Set exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
		configurations = filter(configurations, autoConfigurationMetadata);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return configurations.toArray(new String[configurations.size()]);
	}
	catch (IOException ex) {
		throw new IllegalStateException(ex);
	}
}

上面的代码逻辑还是挺清晰的。

  • getCandidateConfigurations :通过 Spring 工厂加载机制加载 META-INF/spring.factories 下以 org.springframework.boot.autoconfigure.EnableAutoConfiguration 以 key 的类全名列表,其实就是需要自动配置的类全名。比如 spring-boot-autoconfigure.jar/META-INF/spring.factories 定义需要自动配置的类。
  • removeDuplicates,通过把获取到需要自动配置的类全名列表添加 Set 中删除重复的自动配置类。
  • sort :通过 AutoConfigurationSorter 对需要自动配置的类进行排序。
  • getExclusions:获取到 Spring Boot 中配置需要排除的自动配置的类然后进行排除。
  • AutoConfigurationImportSelector#filter:通过 Spring 工厂加载机制加载 AutoConfigurationImportFilter 接口的实例默认是 OnClassCondition,来判断这个自动依赖配置是否通过被自动依赖配置。OnClassCondition的判断规则是通过标注在需要自动依赖配置类 @ConditionalOnClass@ConditionalOnMissingClass 这两个注解来进行判断。 @ConditionalOnClass 注解是判断 classpath 中是否包含@ConditionalOnClass指定类,如果包含就满足条件, @ConditionalOnMissingClass 是判断 classpath 中不包含 @ConditionalOnMissingClass 指定的类,如果不包含就满足条件。

下面就是 Spring-boot-autoconfigure.jar 包下关于自动配置相关的配置。里面包含了 Web 开发各种常用组件的自动配置。

spring-boot-autoconfigure.jar/META-INF/spring.factories

# 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.CloudAutoConfiguration,\
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.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
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.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
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.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.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.autoconfigure.mobile.DeviceResolverAutoConfiguration,\
org.springframework.boot.autoconfigure.mobile.DeviceDelegatingViewResolverAutoConfiguration,\
org.springframework.boot.autoconfigure.mobile.SitePreferenceAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.reactor.ReactorAutoConfiguration,\
org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration,\
org.springframework.boot.autoconfigure.social.FacebookAutoConfiguration,\
org.springframework.boot.autoconfigure.social.LinkedInAutoConfiguration,\
org.springframework.boot.autoconfigure.social.TwitterAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration

比如 Redis 的自动配置类 RedisAutoConfiguration.java

RedisAutoConfiguration.java

@Configuration
@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class })
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {
}

要满足 Redis 自动配置的类满足的条件就是在 classpath 中包含 JedisConnection.classJedis.class以及 RedisOperations.class。然后我们通过添加下面的包就会添加 Redis 相关的依赖包到 classpath,就可以进行 Redis 的自动依赖注入了。

 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-redis</artifactId>
     <version>${
     spring.boot-version}</version>
 </dependency>

通过上面的讲解我们可以知道自动依赖配置的核心还是通过 OnClassCondition 这个类进行条件判断.那么这个类的原理又是什么呢。其实这个类是继承了 SpringBootCondition,而SpringBootCondition又是实现了Condition 这个接口的。那么这个接口又是何方圣神呢?

4、Condition 使用示例

这个注解并不是 Spring Boot 项目中特有的,而是 Spring Framework 在 4.0 之中出现的新特性。它的作用就是通过条件判读 Spring Bean 是否通过注入到 Spring 容器当中。下面就是这个接口的定义。

public interface Condition {

	/**
	 * Determine if the condition matches.
	 * @param context the condition context
	 * @param metadata metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
	 * or {@link org.springframework.core.type.MethodMetadata method} being checked.
	 * @return {@code true} if the condition matches and the component can be registered
	 * or {@code false} to veto registration.
	 */
	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

}

举个简单的例子:

4.1 Os

操作系统类,保存操作系统的名称

Os.java

@Data
@AllArgsConstructor
public class Os {

    private String name;

}

4.2 WindowsCondition

Condition 接口实现类,从环境变量中获取系统名称,看是否是 Windows 操作系统。

WindowsCondition.java

public class WindowsCondition implements Condition {

    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        //获取当前环境信息
        Environment environment = conditionContext.getEnvironment();

        //获得当前系统名
        String property = environment.getProperty("os.name");
        //包含Windows则说明是windows系统,返回true
        if (property.contains("Windows")) {
            return true;
        }
        return false;
    }
}

4.3 MacBookCondition

Condition 接口实现类,从环境变量中获取系统名称,看是否是 Mac 操作系统。

MacBookCondition.java

public class MacBookCondition implements Condition {

    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {

        Environment environment = conditionContext.getEnvironment();

        String property = environment.getProperty("os.name");
        if (property.contains("Mac OS X")){
            return true;
        }
        return false;
    }
}

4.4 BeanConfig

BeanConfig 通过 Class 与条件注解来配置 Spring Bean。根据不同的操作系统加载不同的 Os 对象。

BeanConfig.java

@Configuration
public class BeanConfig {

    /**
     * 如果 WindowsCondition 的实现方法返回true,则注入这个 bean
     * @return
     */
    @Conditional({WindowsCondition.class})
    @Bean(name = "windows")
    public Os windows(){
        return new Os("windows");
    }

    /**
     * 如果 LinuxCondition 的实现方法返回 true,则注入这个 bean
     * @return
     */
    @Conditional({MacBookCondition.class})
    @Bean("macBook")
    public Os macBook(){
        return new Os("mac");
    }
}

4.5 ConditionTest

测试类,用于测试 Condition 接口是否生效。

ConditionTest.java

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {
     BeanConfig.class})
public class ConditionTest implements ApplicationContextAware {
     

    private ApplicationContext applicationContext;

    @Test
    public void test(){
     
        String osName = applicationContext.getEnvironment().getProperty("os.name");
        System.out.println("当前系统为:" + osName);
        Os os = applicationContext.getBean(Os.class);
        System.out.println(os);

    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
     
        this.applicationContext = applicationContext;
    }
}

运行测试类如下:

Spring Boot 自动配置原理解析_第2张图片

5、Condition 实现原理

Spring Framework 3.0 之后可以定义 Class 类型的 Bean,也就是通过 @Configuration@Bean 代替 Spring 的 xml 类型的 bean。它的处理类是 BeanDefinitionRegistryPostProcessor 这个接口的实现类 ConfigurationClassPostProcessor。这个类的主要作用就是解析带有 @Configuration 把它解析成 Spring Bean,然后如果标注了 @Configuration 类里面如果有 @Bean 的方法就会以 factoryMethodName 的形式来创建这个 @Bean

Spring 处理 Class 类型的 Bean 到 Condition 的时序图
Spring Boot 自动配置原理解析_第3张图片

下面就是 ConfigurationClassParser 处理标注了 @Configuration 的注解,如果 ConditionEvaluator 判断这个需要跳过,那么这个配置类就不是加载到 Spring 容器当中去。

ConfigurationClassParser#processConfigurationClass

	protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
     
		if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
     
			return;
		}

		ConfigurationClass existingClass = this.configurationClasses.get(configClass);
		if (existingClass != null) {
     
			if (configClass.isImported()) {
     
				if (existingClass.isImported()) {
     
					existingClass.mergeImportedBy(configClass);
				}
				// Otherwise ignore new imported config class; existing non-imported class overrides it.
				return;
			}
			else {
     
				// Explicit bean definition found, probably replacing an import.
				// Let's remove the old one and go with the new one.
				this.configurationClasses.remove(configClass);
				for (Iterator<ConfigurationClass> it = this.knownSuperclasses.values().iterator(); it.hasNext();) {
     
					if (configClass.equals(it.next())) {
     
						it.remove();
					}
				}
			}
		}

		// Recursively process the configuration class and its superclass hierarchy.
		SourceClass sourceClass = asSourceClass(configClass);
		do {
     
			sourceClass = doProcessConfigurationClass(configClass, sourceClass);
		}
		while (sourceClass != null);

		this.configurationClasses.put(configClass, configClass);
	}

下面就是ConditionEvaluator#shouldSkip()判断是否跳过的逻辑。

ConditionEvaluator#shouldSkip()

	public boolean shouldSkip(AnnotatedTypeMetadata metadata, ConfigurationPhase phase) {
     
		if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
     
			return false;
		}

		if (phase == null) {
     
			if (metadata instanceof AnnotationMetadata &&
					ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
     
				return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
			}
			return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
		}

		List<Condition> conditions = new ArrayList<Condition>();
		for (String[] conditionClasses : getConditionClasses(metadata)) {
     
			for (String conditionClass : conditionClasses) {
     
				Condition condition = getCondition(conditionClass, this.context.getClassLoader());
				conditions.add(condition);
			}
		}

		AnnotationAwareOrderComparator.sort(conditions);

		for (Condition condition : conditions) {
     
			ConfigurationPhase requiredPhase = null;
			if (condition instanceof ConfigurationCondition) {
     
				requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
			}
			if (requiredPhase == null || requiredPhase == phase) {
     
				if (!condition.matches(this.context, metadata)) {
     
					return true;
				}
			}
		}

		return false;
	}
  • 如果元数据也就是标注 @Configuration 为空,或者这个元数据并没有标注@Conditional返回 false,则这个配置类配置的所有 bean 需要加入 Spring 容器
  • 获取这个元数据类上面所有标注的 @Conditional 注解的条件类 Condition 列表
  • 排序获取到这些条件类 Condition 列表.
  • 遍历这些条件类 Condition,只要其中一个不满足条件就返回 ture,这个配置类就不能够被加入到 Spring 容器当中
  • 所有的条件类 Condition都满足条件,这时返回 false,则这个配置类配置的所有 bean 需要加入 Spring 容器

6、SpringBootCondition

SpringBootCondition 是一个抽象类,它继承了 Condition 接口,所有的 Spring Boot 的条件注解实现类都继承了这个类通过实现它的SpringBootCondition#getMatchOutcome 来实现不同的条件注入实现。

6.1 Class 匹配

基于 Class 的条件注解:@ConditionalOnClass(类加载器中存在指明的类)或者@ConditionalOnMissingClass(类加载器中不存在指明的类)。其实就是我们上面所说的 OnClassCondition,下面我们来看一下具体的实现:

OnClassCondition.java

@Order(Ordered.HIGHEST_PRECEDENCE)
class OnClassCondition extends SpringBootCondition
		implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware {
     
	@Override
	public ConditionOutcome getMatchOutcome(ConditionContext context,
			AnnotatedTypeMetadata metadata) {
     
		ClassLoader classLoader = context.getClassLoader();
		ConditionMessage matchMessage = ConditionMessage.empty();
		List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
		if (onClasses != null) {
     
			List<String> missing = getMatches(onClasses, MatchType.MISSING, classLoader);
			if (!missing.isEmpty()) {
     
				return ConditionOutcome
						.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
								.didNotFind("required class", "required classes")
								.items(Style.QUOTE, missing));
			}
			matchMessage = matchMessage.andCondition(ConditionalOnClass.class)
					.found("required class", "required classes").items(Style.QUOTE,
							getMatches(onClasses, MatchType.PRESENT, classLoader));
		}
		List<String> onMissingClasses = getCandidates(metadata,
				ConditionalOnMissingClass.class);
		if (onMissingClasses != null) {
     
			List<String> present = getMatches(onMissingClasses, MatchType.PRESENT,
					classLoader);
			if (!present.isEmpty()) {
     
				return ConditionOutcome.noMatch(
						ConditionMessage.forCondition(ConditionalOnMissingClass.class)
								.found("unwanted class", "unwanted classes")
								.items(Style.QUOTE, present));
			}
			matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class)
					.didNotFind("unwanted class", "unwanted classes").items(Style.QUOTE,
							getMatches(onMissingClasses, MatchType.MISSING, classLoader));
		}
		return ConditionOutcome.match(matchMessage);
	}
	    // 枚举:匹配类型。用于查询类名在对应的类加载器中是否存在。
		private enum MatchType {
     
        // 匹配成功
		PRESENT {
     

			@Override
			public boolean matches(String className, ClassLoader classLoader) {
     
				return isPresent(className, classLoader);
			}

		},
        // 匹配不成功
		MISSING {
     

			@Override
			public boolean matches(String className, ClassLoader classLoader) {
     
				return !isPresent(className, classLoader);
			}

		};

		private static boolean isPresent(String className, ClassLoader classLoader) {
     
			if (classLoader == null) {
     
				classLoader = ClassUtils.getDefaultClassLoader();
			}
			try {
     
				forName(className, classLoader);
				return true;
			}
			catch (Throwable ex) {
     
				return false;
			}
		}

		private static Class<?> forName(String className, ClassLoader classLoader)
				throws ClassNotFoundException {
     
			if (classLoader != null) {
     
				return classLoader.loadClass(className);
			}
			return Class.forName(className);
		}

		public abstract boolean matches(String className, ClassLoader classLoader);

	}
}
  • 获取到 @ConditionalOnClass 注解内添加的 Class 全名类列表,然后在当前类加载器获取这个类,如果有一个找不到就返回 false。
  • 获取到 @ConditionalOnMissingClass 注解内添加的 Class 全名类列表,然后在当前类加载器获取这个类,如果能够有一个类找到就返回 false。
  • 否则返回 ture。

6.2 Spring bean 匹配

基于 Spring Bean 的条件注解:@ConditionalOnBean(Spring容器中存在指明的bean)、@ConditionalOnMissingBean(Spring容器中不存在指明的bean)以及ConditionalOnSingleCandidate(Spring容器中存在且只存在一个指明的bean)都是基于Bean的条件注解,它们对应的条件类是 ConditionOnBean

@ConditionOnBean注解定义如下:

@Target({
      ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {
     

   // 匹配的bean类型
  Class<?>[] value() default {
     }; 

  // 匹配的bean类型的类名
  String[] type() default {
     }; 
  
   // 匹配的bean注解
  Class<? extends Annotation>[] annotation() default {
     };

  // 匹配的bean的名字
  String[] name() default {
     }; 

  // 搜索策略。提供CURRENT(只在当前容器中找)、PARENTS(只在所有的父容器中找;但是不包括当前容器)和ALL(CURRENT和PARENTS的组合)
  SearchStrategy search() default SearchStrategy.ALL; 
}

OnBeanCondition 代码实现如下:

OnBeanCondition.java

@Order(Ordered.LOWEST_PRECEDENCE)
class OnBeanCondition extends SpringBootCondition implements ConfigurationCondition {
@Override
	public ConditionOutcome getMatchOutcome(ConditionContext context,
			AnnotatedTypeMetadata metadata) {
		ConditionMessage matchMessage = ConditionMessage.empty();
		if (metadata.isAnnotated(ConditionalOnBean.class.getName())) {
			BeanSearchSpec spec = new BeanSearchSpec(context, metadata,
					ConditionalOnBean.class);
			List matching = getMatchingBeans(context, spec);
			if (matching.isEmpty()) {
				return ConditionOutcome.noMatch(
						ConditionMessage.forCondition(ConditionalOnBean.class, spec)
								.didNotFind("any beans").atAll());
			}
			matchMessage = matchMessage.andCondition(ConditionalOnBean.class, spec)
					.found("bean", "beans").items(Style.QUOTE, matching);
		}
		if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {
			BeanSearchSpec spec = new SingleCandidateBeanSearchSpec(context, metadata,
					ConditionalOnSingleCandidate.class);
			List matching = getMatchingBeans(context, spec);
			if (matching.isEmpty()) {
				return ConditionOutcome.noMatch(ConditionMessage
						.forCondition(ConditionalOnSingleCandidate.class, spec)
						.didNotFind("any beans").atAll());
			}
			else if (!hasSingleAutowireCandidate(context.getBeanFactory(), matching,
					spec.getStrategy() == SearchStrategy.ALL)) {
				return ConditionOutcome.noMatch(ConditionMessage
						.forCondition(ConditionalOnSingleCandidate.class, spec)
						.didNotFind("a primary bean from beans")
						.items(Style.QUOTE, matching));
			}
			matchMessage = matchMessage
					.andCondition(ConditionalOnSingleCandidate.class, spec)
					.found("a primary bean from beans").items(Style.QUOTE, matching);
		}
		if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
			BeanSearchSpec spec = new BeanSearchSpec(context, metadata,
					ConditionalOnMissingBean.class);
			List matching = getMatchingBeans(context, spec);
			if (!matching.isEmpty()) {
				return ConditionOutcome.noMatch(ConditionMessage
						.forCondition(ConditionalOnMissingBean.class, spec)
						.found("bean", "beans").items(Style.QUOTE, matching));
			}
			matchMessage = matchMessage.andCondition(ConditionalOnMissingBean.class, spec)
					.didNotFind("any beans").atAll();
		}
		return ConditionOutcome.match(matchMessage);
	}
		private List getMatchingBeans(ConditionContext context,
			BeanSearchSpec beans) {
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		if (beans.getStrategy() == SearchStrategy.PARENTS
				|| beans.getStrategy() == SearchStrategy.ANCESTORS) {
			BeanFactory parent = beanFactory.getParentBeanFactory();
			Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent,
					"Unable to use SearchStrategy.PARENTS");
			beanFactory = (ConfigurableListableBeanFactory) parent;
		}
		if (beanFactory == null) {
			return Collections.emptyList();
		}
		List beanNames = new ArrayList();
		boolean considerHierarchy = beans.getStrategy() != SearchStrategy.CURRENT;
		for (String type : beans.getTypes()) {
			beanNames.addAll(getBeanNamesForType(beanFactory, type,
					context.getClassLoader(), considerHierarchy));
		}
		for (String ignoredType : beans.getIgnoredTypes()) {
			beanNames.removeAll(getBeanNamesForType(beanFactory, ignoredType,
					context.getClassLoader(), considerHierarchy));
		}
		for (String annotation : beans.getAnnotations()) {
			beanNames.addAll(Arrays.asList(getBeanNamesForAnnotation(beanFactory,
					annotation, context.getClassLoader(), considerHierarchy)));
		}
		for (String beanName : beans.getNames()) {
			if (containsBean(beanFactory, beanName, considerHierarchy)) {
				beanNames.add(beanName);
			}
		}
		return beanNames;
	}
}
  • 判断当前 Class 是否标注了 @ConditionalOnBean ,获取到注解标注的 beanName 的列表信息,并且根据搜索策略搜索看列表中是否有不包含的 beanName。如果包含就直接返回 false。
  • 判断当前 Class 是否标注了 @ConditionalOnSingleCandidate ,获取到注解标注的 beanName 的列表信息,并且根据搜索策略搜索是否容器中是否存在且只存在一个声明的 Spring bean,如果有一个 bean 包含多个就直接返回 false。
  • 判断当前 Class 是否标注了 @ConditionalOnMissingBean ,获取到注解标注的 beanName 的列表信息,并且根据搜索策略搜索,看列表中如果能够搜索到至少 beanName 就直接返回 false。
  • 其它情况说明都满足 Spring Bean 匹配条件,就可以返回 true。

6.3 其它

SpringBoot 还提供了其他比如ConditionalOnJavaConditionalOnNotWebApplicationConditionalOnWebApplicationConditionalOnResourceConditionalOnPropertyConditionalOnExpression等条件注解,有兴趣的读者可以自行查看它们的底层处理逻辑。

各种条件注解描述:

条件注解 对应的Condition处理类 处理逻辑
@ConditionalOnBean OnBeanCondition Spring容器中是否存在对应的实例。可以通过实例的类型、类名、注解、昵称去容器中查找(可以配置从当前容器中查找或者父容器中查找或者两者一起查找)这些属性都是数组,通过”与”的关系进行查找
@ConditionalOnClass OnClassCondition 类加载器中是否存在对应的类。可以通过Class指定(value属性)或者Class的全名指定(name属性)。如果是多个类或者多个类名的话,关系是”与”关系,也就是说这些类或者类名都必须同时在类加载器中存在
@ConditionalOnExpression OnExpressionCondition 判断SpEL 表达式是否成立
@ConditionalOnJava OnJavaCondition 指定Java版本是否符合要求。内部有2个属性value和range。value表示一个枚举的Java版本,range表示比这个老或者新于等于指定的Java版本(默认是新于等于)。内部会基于某些jdk版本特有的类去类加载器中查询,比如如果是jdk9,类加载器中需要存在java.security.cert.URICertStoreParameters;如果是jdk8,类加载器中需要存在java.util.function.Function;如果是jdk7,类加载器中需要存在java.nio.file.Files;如果是jdk6,类加载器中需要存在java.util.ServiceLoader
@ConditionalOnMissingBean OnBeanCondition Spring容器中是否缺少对应的实例。可以通过实例的类型、类名、注解、昵称去容器中查找(可以配置从当前容器中查找或者父容器中查找或者两者一起查找)这些属性都是数组,通过”与”的关系进行查找。还多了2个属性ignored(类名)和ignoredType(类名),匹配的过程中会忽略这些bean
@ConditionalOnMissingClass OnClassCondition 跟ConditionalOnClass的处理逻辑一样,只是条件相反,在类加载器中不存在对应的类
@ConditionalOnNotWebApplication OnWebApplicationCondition 应用程序是否是非Web程序,没有提供属性,只是一个标识。会从判断Web程序特有的类是否存在,环境是否是Servlet环境,容器是否是Web容器等
@ConditionalOnProperty OnPropertyCondition 应用环境中的屬性是否存在。提供prefix、name、havingValue以及matchIfMissing属性。prefix表示属性名的前缀,name是属性名,havingValue是具体的属性值,matchIfMissing是个boolean值,如果属性不存在,这个matchIfMissing为true的话,会继续验证下去,否则属性不存在的话直接就相当于匹配不成功
@ConditionalOnResource OnResourceCondition 是否存在指定的资源文件。只有一个属性resources,是个String数组。会从类加载器中去查询对应的资源文件是否存在
@ConditionalOnSingleCandidate OnBeanCondition Spring容器中是否存在且只存在一个对应的实例。只有3个属性value、type、search。跟ConditionalOnBean中的这3种属性值意义一样
@ConditionalOnWebApplication OnWebApplicationCondition 应用程序是否是Web程序,没有提供属性,只是一个标识。会从判断Web程序特有的类是否存在,环境是否是Servlet环境,容器是否是Web容器等

至此 Spring Boot 自动配置原理就分析完了。

你可能感兴趣的:(Spring,boot,Spring,Boot,&,Cloud)