相信大家都利用过springboot搭建过项目,springboot的强大之处在于起步依赖与自动配置,关于sprinboot的自动配置一直感觉十分的神奇,这几天查看源码研究了一下,基本明白了运行原理,总结一下。
总结一句话:springboot通过自动配置类完成自动配置。
springboot运用自动化配置的方法是:将自动配置类导入spring容器中,同时在配置类的方法上定义多个特殊化的条件化注解,当满足所有的条件后执行方法完成自动配置。
大体上可分为两步:
1.利用AutoConfigurationImportSelector的selectImports方法将自动配置类导入Spring容器。
2.自动配置类加载向Spring容器中注入Bean。.
在springboot(1.5.19版本)中有96个自动配置类,那在我们启动项目时是不是需要加载所有的自动配置类?答案肯定不是,那springboot如何去判断应该引入哪些自动配置类。这部分工作由AutoConfigurationImportSelector类的selectImports方法完成。
来看看AutoConfigurationImportSelector类的selectImports方法都做了哪些事
// 获得需要导入的自动配置类的路径
@Override
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);
// 获得需要排除的自动配置类路径,映射的是在pom文件中使用exclutions排除的类
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);
}
}
selectImports大概做了四件事:
在selectImports方法中调用getCandidateConfigurations方法获得所有的自动配置类
protected List getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
// 获取所有自动配置类路径
List 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;
}
利用SpringFactoriesLoader的loadFactoryNames方法获取所有自动配置类路径。
public static List loadFactoryNames(Class> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
try {
// 获取所有的自动配置类路径
Enumeration urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List result = new ArrayList();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String propertyValue = properties.getProperty(factoryClassName);
for (String factoryName : StringUtils.commaDelimitedListToStringArray(propertyValue)) {
result.add(factoryName.trim());
}
}
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
我们发现最终通过类加载器classLoader的getResources方法获取类路径,获取的地址是:
public abstract class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
}
而"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,
......
总结:
所有的自动配置类路径都存放在一个spring.factories文件中,在我们每次在项目运行时从这个文件中拿到所有的自动配置类路径。接下来移除不需要加载的配置类路径。
在springboot加载了自动配置类,此时自动配置类的配置运行机制是什么?解决这个问题我们利用RedisAutoConfiguration类(redis的自动配置类)来分析。
我们来看一下RedisAutoConfiguration的类头部是如何描述的
@Configuration
@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class })
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {
......
}
@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class })
判断classpath里有没有JedisConnection,RedisOperations,Jedis这三个类。在classpath路径下三个类均存在时redis的自动配置才会生效。
@EnableConfigurationProperties(RedisProperties.class)
引入RedisProperties类。
我们看一下RedisProperties中包含什么。
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
private int database = 0;
private String url;
private String host = "localhost";
private String password;
private int port = 6379;
private boolean ssl;
private int timeout;
private Pool pool;
private Sentinel sentinel;
private Cluster cluster;
}
@ConfigurationProperties(prefix = "spring.redis")
在application.property中映射到该类的属性的前缀是spring.redis。
我们在application.property中配置所有带有spring.redis前缀的属性都会注入到这个类相应的属性中。RedisProperties类会被加载到RedisAutoConfiguration中,为RedisAutoConfiguration提供配置参数。
我们回到RedisAutoConfiguration类中,假设@ConditionalOnClass条件成立,并且成功引入POJO类RedisProperties。
我们抽出redisTemplate方法,该方法判断spring是否已经配置了redisTemplate的Bean,如果没有配置该Bean则执行redisTemplate方法,利用@Bean注解将RedisTemplate注入到spring容器中。
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate
此时利用@ConditionalOnMissingBean注解就完成了RedisTemplate的注入。
条件化注解 | 配置生效条件 |
@ConditionalOnBean | 配置了某个特定的Bean |
@ConditionalOnMissingBean | 没有配置特定的Bean |
@ConditionalOnClass | Classpath里有指定的类 |
@ConditionalOnMissingClass | Classpath里没有指定的类 |
@ConditionalOnExpression | 给定的SpEL表达式计算结果为true |
@ConditionalOnJava | Java版本匹配特定值或者一个范围值 |
@ConditionalOnProperty | 指定的配置属性要有一个明确的值 |
@ConditionalOnResource | Classpath里有指定的资源 |
@ConditionalOnWebApplication | 这是一个Web应用程序 |
@ConditionalOnNotWebApplication | 这不是一个Web应用程序 |
通过springboot的起步依赖与自动配置,可以更加快速,便携的开发Spring应用程序。自动配置把你从样板式的配置中解放出来。这些配置在没有springboot的spring应用程序中十分常见。