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 是如何做到的呢?下面我们就来揭开它神秘的面纱。
下面我们就基于 Maven 作项目管理使用 Spring Boot 开发一个简单的 Web 项目。
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>
写一个类 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);
}
}
在浏览器访问 http:localhost:8080
,就会显示 index
。
这样一个简单的 web 项目就完成了, 使用 Spring Boot 开发一个 web 项目是不是特别简单。然后如果集成 redis 就只需要添加上面 redis 的依赖就行了。
从上面的例子我们可以看出 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
注解是一个组合注解,那我们就从它包含的注解以及它的方法来分析它。
@SpringBootApplication
注解是一个组合注解,在它上面包含: @SpringBootConfiguration
、@EnableAutoConfiguration
和 ComponentScan
这三个注解。
@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。
@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 的定义。
下面我们回到我们的自动依赖配置, 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.class
、Jedis.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
这个接口的。那么这个接口又是何方圣神呢?
这个注解并不是 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);
}
举个简单的例子:
操作系统类,保存操作系统的名称
Os.java
@Data
@AllArgsConstructor
public class Os {
private String name;
}
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;
}
}
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;
}
}
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");
}
}
测试类,用于测试 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 Framework 3.0 之后可以定义 Class 类型的 Bean,也就是通过 @Configuration
和 @Bean
代替 Spring 的 xml 类型的 bean。它的处理类是 BeanDefinitionRegistryPostProcessor
这个接口的实现类 ConfigurationClassPostProcessor
。这个类的主要作用就是解析带有 @Configuration
把它解析成 Spring Bean,然后如果标注了 @Configuration
类里面如果有 @Bean
的方法就会以 factoryMethodName
的形式来创建这个 @Bean
。
下面就是 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 容器SpringBootCondition 是一个抽象类,它继承了 Condition 接口,所有的 Spring Boot 的条件注解实现类都继承了这个类通过实现它的SpringBootCondition#getMatchOutcome
来实现不同的条件注入实现。
基于 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。基于 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;
}
}
@ConditionalOnBean
,获取到注解标注的 beanName 的列表信息,并且根据搜索策略搜索看列表中是否有不包含的 beanName。如果包含就直接返回 false。@ConditionalOnSingleCandidate
,获取到注解标注的 beanName 的列表信息,并且根据搜索策略搜索是否容器中是否存在且只存在一个声明的 Spring bean,如果有一个 bean 包含多个就直接返回 false。@ConditionalOnMissingBean
,获取到注解标注的 beanName 的列表信息,并且根据搜索策略搜索,看列表中如果能够搜索到至少 beanName 就直接返回 false。SpringBoot 还提供了其他比如ConditionalOnJava
、ConditionalOnNotWebApplication
、ConditionalOnWebApplication
、ConditionalOnResource
、ConditionalOnProperty
、ConditionalOnExpression
等条件注解,有兴趣的读者可以自行查看它们的底层处理逻辑。
各种条件注解描述:
条件注解 | 对应的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 自动配置原理就分析完了。