SpringBoot自动加载的原理

  • 首先我们在程序入口的位置点进SpringApplication这个类的run方法
1
2
3
4
5
6
7
@SpringBootApplication
public class Application {

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

run方法点进去

1
2
3
public static ConfigurableApplicationContext run(Object source, String... args) {
return run(new Object[] { source }, args);
}

继续

1
2
3
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
return new SpringApplication(sources).run(args);
}

可以看到run方法只是创建了SpringApplication对象,并执行run方法。继续跟进

1
2
3
public SpringApplication(Object... sources) {
initialize(sources);
}

initialize具体代码:

1
2
3
4
5
6
7
8
9
10
11
@SuppressWarnings({ "unchecked", "rawtypes" })
private void initialize(Object[] sources) {
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
this.webEnvironment = deduceWebEnvironment();
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}

getSpringFactoriesInstances点进去:

1
2
3
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}

继续:

1
2
3
4
5
6
7
8
9
10
11
12
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<String>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}

loadFactoryNames点进去:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
try {
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List<String> result = new ArrayList<String>();
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);
}
}

可以看到常量 FACTORIES_RESOURCE_LOCATION 的定义:

1
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

找到org.springframework.boot.autoconfigurespring.factories文件:

以Redis为例,我们可以看到spring.factories文件中有Redis的配置

1
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
@Configuration
@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class })
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {

/**
* Redis connection configuration.
*/
@Configuration
@ConditionalOnClass(GenericObjectPool.class)
protected static class RedisConnectionConfiguration {

private final RedisProperties properties;

private final RedisSentinelConfiguration sentinelConfiguration;

private final RedisClusterConfiguration clusterConfiguration;

public RedisConnectionConfiguration(RedisProperties properties,
ObjectProvider<RedisSentinelConfiguration> sentinelConfiguration,
ObjectProvider<RedisClusterConfiguration> clusterConfiguration) {
this.properties = properties;
this.sentinelConfiguration = sentinelConfiguration.getIfAvailable();
this.clusterConfiguration = clusterConfiguration.getIfAvailable();
}

@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
public JedisConnectionFactory redisConnectionFactory()
throws UnknownHostException {
return applyProperties(createJedisConnectionFactory());
}

protected final JedisConnectionFactory applyProperties(
JedisConnectionFactory factory) {
configureConnection(factory);
if (this.properties.isSsl()) {
factory.setUseSsl(true);
}
factory.setDatabase(this.properties.getDatabase());
if (this.properties.getTimeout() > 0) {
factory.setTimeout(this.properties.getTimeout());
}
return factory;
}
...

说明:

  1. @ConditionalOnClass激活一个配置,当类路径中存在这个类时才会配置该类
  2. @EnableConfigurationProperties 自动映射一个POJO从Spring Boot配置文件(默认是application.properties文件)的属性集。
  3. @ConditionalOnMissingBean 启用一个Bean定义,但必须是这个Bean之前未定义过才有效,还可以使用@ AutoConfigureBefore@AutoConfigureAfter注解来定义这些配置类的载入顺序

具体几个@Conditon*注解的含义

  • @ConditionalOnBean
    仅仅在当前上下文中存在某个对象时,才会实例化一个Bean

  • @ConditionalOnClass
    某个class位于类路径上,才会实例化一个Bean),该注解的参数对应的类必须存在,否则不解析该注解修饰的配置类

  • @ConditionalOnExpression
    当表达式为true的时候,才会实例化一个Bean

  • @ConditionalOnMissingBean
    仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean,该注解表示,如果存在它修饰的类的bean,则不需要再创建这个bean,可以给该注解传入参数例如@ConditionOnMissingBean(name = “example”),这个表示如果name为“example”的bean存在,这该注解修饰的代码块不执行

  • @ConditionalOnMissingClass
    某个class类路径上不存在的时候,才会实例化一个Bean

  • @ConditionalOnNotWebApplication
    不是web应用时,才会执行

如若想关闭自动配置时我们就是把spring.factories文件不需要自动配置的类过滤掉
比如:

1
2
3
4
5
6
7
@SpringBootApplication(exclude = {RedisAutoConfiguration.class})
public class Application {

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

  • 总结

SpringBoot 的 自动配置得益于 SpringFramework 强大的支撑,框架早已有很多工具和注解可以自动装配 Bean 。SpringBoot 通过 一个封装,将市面上通用的组件直接写好了配置类。当我们程序去依赖了这些组件的 jar 包后,启动 SpringBoot应用,于是自动加载开始了。

我们也可以定义自己的自动装配组件,依赖之后,Spring直接可以加载我们定义的 starter 。笔者将在后续文章中进行编码和解读