问题的提出:Spring Boot不像spring需要定义xml文件文件,那么spring boot是如何在没有配置文件的情况下为我们启动一个完整的WEB工程的呢。我们先@SpringBootApplication开始分析下这个启动流程,源代码取自:1.4.1.RELEASE。
我们先看下@SpringBootApplication的代码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class))
public @interface SpringBootApplication {}
可以看到这里是一个复合注解,如:@Target,@Document,@Retention是我们常见的注解,这里重点说下@SpringBootConfiguration注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {}
这个注解重要的就是@Configuration:这是标注当前类为Java Config类。
在@SpringBootApplication中另外一个重要的注解就是@EnableAutoConfiguration:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}
这个注解上@Import(EnableAutoConfigurationImportSelector.class)代表引入其它的Spring的JavaConfig接着进入EnableAutoConfigurationImportSelector.class。
关注一下以下的方法:
@Override
public String[] selectImports(AnnotationMetadata metadata) {
if (!isEnabled(metadata)) {
returnNO_IMPORTS;
}
try {
AnnotationAttributes attributes = getAttributes(metadata);
List
attributes);
configurations = removeDuplicates(configurations);
Set
configurations.removeAll(exclusions);
configurations = sort(configurations);
recordWithConditionEvaluationReport(configurations, exclusions);
return configurations.toArray(new String[configurations.size()]);
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
进入:List configurations = getCandidateConfigurations(metadata,attributes);
protected List
AnnotationAttributes attributes) {
List
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;
}
在进入:List configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
代码如下:
public static List
String factoryClassName = factoryClass.getName();
try {
Enumeration
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
"] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
在上面的代码可以看到自动配置器会跟根据传入的factoryClass.getName()到spring.factories的文件中找到相应的key,从而加载里面的类但我们打开spring-boot-autoconfigure-1.4.1.RELEASE.jar里面的spring.factories可以发现很多key。查看spring.factories文件:
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# 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.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
这里以org.springframework.boot.autoconfigure.data.Redis.RedisAutoConfiguration为例查看代码如下:
@Configuration
@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class })
@EnableConfigurationProperties
public class RedisAutoConfiguration {
@Bean(name = "org.springframework.autoconfigure.redis.RedisProperties")
@ConditionalOnMissingBean
public RedisProperties redisProperties() {
return new RedisProperties();
}
/**
* Redis connection configuration.
*/
@Configuration
@ConditionalOnMissingClass("org.apache.commons.pool2.impl.GenericObjectPool")
protected static class RedisConnectionConfiguration
extends AbstractRedisConfiguration {
@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
public JedisConnectionFactory redisConnectionFactory()
throws UnknownHostException {
return applyProperties(new JedisConnectionFactory(getSentinelConfig()));
}
}
//省略部分代码…
/**
* Standard Redis configuration.
*/
@Configuration
protected static class RedisConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate
RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
}
把类简化一下基本上就可以看出这就是一个Spring的注解版的配置
(1)@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class })这个注解的意思是:当存在JedisConnection.class, RedisOperations.class, Jedis.class三个类时才解析RedisAutoConfiguration配置类,否则不解析这一个配置类
(2)@ConditionalOnMissingBean(name = “redisTemplate”)这个注解的意思是如果容器中不存在name指定的bean则创建bean注入,否则不执行
(3)内部代码可以看出里面又定义了两个带@Configuration注解的配置类,这两个配置类会向SpringIOC容器注入可能3个bean:
首先当类路径下存在(GenericObjectPool.class)时则注入JedisConnectionFactory 的实例如果Spring容器中不存在name = “redisTemplate”的实体,则创建RedisTemplate和StringRedisTemplate实例注入容器,这样在Spring的项目中,就可以用在任意的Spring管理的bean中注册用RedisTemplate和StringRedisTemplate的实例来对redis进入操作了。
通过以上分析的过程我们可以发现只要一个基于SpringBoot项目的类路径下存在JedisConnection.class, RedisOperations.class, Jedis.class就可以触发自动化配置,意思说我们只要在maven的项目中依赖了spring-data-redis-1.7.2.RELEASE.jar和C:jedis-2.8.2.jar就可以触发自动配置,但这样不是每集成一个功能都要去分析里其自动化配置类,那就代不到开箱即用的效果了。所以Spring-boot为我提供了统一的starter可以直接配置好相关触发自动配置的所有的类的依赖集如redis的start如下:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
因为maven依赖的传递性,我们只要依赖starter就可以看在类路径下配置好所有的触发自动配置的所有类,实现开箱即用的功能。