SpringBoot源码--自动配置分析

前言

相信大家都利用过springboot搭建过项目,springboot的强大之处在于起步依赖与自动配置,关于sprinboot的自动配置一直感觉十分的神奇,这几天查看源码研究了一下,基本明白了运行原理,总结一下。

1. springboot如何完成自动配置?

总结一句话:springboot通过自动配置类完成自动配置。

springboot运用自动化配置的方法是:将自动配置类导入spring容器中,同时在配置类的方法上定义多个特殊化的条件化注解,当满足所有的条件后执行方法完成自动配置。

大体上可分为两步:

1.利用AutoConfigurationImportSelector的selectImports方法将自动配置类导入Spring容器。

2.自动配置类加载向Spring容器中注入Bean。.

2. 引入自动配置类

在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大概做了四件事:

  1. 获取所有的自动配置类路径
  2. 移除重复类路径
  3. 移除在pom文件中手动移除的类路径
  4. 移除不需要加载的类路径

2.1springboot如何获取所有的自动配置类?

在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文件中,在我们每次在项目运行时从这个文件中拿到所有的自动配置类路径。接下来移除不需要加载的配置类路径。

3.自动配置类如何完成自动配置?

在springboot加载了自动配置类,此时自动配置类的配置运行机制是什么?解决这个问题我们利用RedisAutoConfiguration类(redis的自动配置类)来分析。

3.1引入属性POJO类

我们来看一下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提供配置参数。

3.2自动注入Bean

我们回到RedisAutoConfiguration类中,假设@ConditionalOnClass条件成立,并且成功引入POJO类RedisProperties。

我们抽出redisTemplate方法,该方法判断spring是否已经配置了redisTemplate的Bean,如果没有配置该Bean则执行redisTemplate方法,利用@Bean注解将RedisTemplate注入到spring容器中。

@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate redisTemplate(
		RedisConnectionFactory redisConnectionFactory)
		throws UnknownHostException {
	RedisTemplate template = new RedisTemplate();
	template.setConnectionFactory(redisConnectionFactory);
	return template;
}

此时利用@ConditionalOnMissingBean注解就完成了RedisTemplate的注入。

自动配置中使用的条件化注解
条件化注解 配置生效条件
@ConditionalOnBean 配置了某个特定的Bean
@ConditionalOnMissingBean 没有配置特定的Bean
@ConditionalOnClass Classpath里有指定的类
@ConditionalOnMissingClass Classpath里没有指定的类
@ConditionalOnExpression 给定的SpEL表达式计算结果为true
@ConditionalOnJava Java版本匹配特定值或者一个范围值
@ConditionalOnProperty 指定的配置属性要有一个明确的值
@ConditionalOnResource Classpath里有指定的资源
@ConditionalOnWebApplication 这是一个Web应用程序
@ConditionalOnNotWebApplication

这不是一个Web应用程序

 

4.总结

通过springboot的起步依赖与自动配置,可以更加快速,便携的开发Spring应用程序。自动配置把你从样板式的配置中解放出来。这些配置在没有springboot的spring应用程序中十分常见。

你可能感兴趣的:(springboot)