0%

springboot的启动过程原理

在这里插入图片描述

原始启动

1
2
3
4
5
6
7
8
@SpringBootApplication
public class SpringbootDemoApplication {

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

}

很明显这是通过@SpringbootApplication注解启动的,通过查看这个注解,发现这个注解除了一些元信息外,最重要的就三个注解分别是:

  • @Configuration: 通过这个注解标注的类,会被认为是spring IOC容器管理bean的工厂类,结合@Bean注解,会把一个方法返回的对象注册到spring IOC容器上下环境中。
  • @ComponentScan:会扫描被@Controller,@Service,包括@Bean 等等注解标注的类,从而把它们注入到spring容器上下文环境中。
  • @EnableAutoConfiguration: 这个注解会自动的把收集来的bean注册到spring容器中,这个最重要了,后面详解。
    代码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    @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 {

@SpringBootConfiguration的自身定义:

1
2
3
4
5
6
7
8
9
10
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}

由上我们可以看到这个也是被@Configuration标注,所以说,springboot的启动类本身也是一个bean。

@EnableAutoConfiguration的定义

1
2
3
4
5
6
7
8
9
10
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
...
}

由上可以看到这个@EnableAutoConfigurations是通过@Import的支持借助AutoConfigurationImportSelector实现自动配置的功能,
这个时候得再往下看AutoConfigurationImportSelector的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* Return the auto-configuration class names that should be considered. By default
* this method will load candidates using {@link SpringFactoriesLoader} with
* {@link #getSpringFactoriesLoaderFactoryClass()}.
* @param metadata the source metadata
* @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
* attributes}
* @return a list of candidate configurations
*/
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> 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;
}

由于内容太多,我们只看加载bean的方法即可,从解释我们可以看这个方法只加载合适的配置类,什么是合适的呢?那就只有被@Configuration,@Service..等等修饰的就可以.
而且是通过spring框架的工具类SpringFactoriesLoader去加载的,那到底是通过读取什么去加载的,我们根本不知道,这个时候再点开SpringFactoriesLoader.loadFactoryNamesz中的方法去看下定义:

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public final class SpringFactoriesLoader {

/**
* The location to look for factories.
* <p>Can be present in multiple JAR files.
*/
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";


private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);

private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();


private SpringFactoriesLoader() {
}
...
/**
* Load the fully qualified class names of factory implementations of the
* given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
* class loader.
* @param factoryType the interface or abstract class representing the factory
* @param classLoader the ClassLoader to use for loading resources; can be
* {@code null} to use the default
* @throws IllegalArgumentException if an error occurs while loading factory names
* @see #loadFactories
*/
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}

try {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}

可以看到这个是加载claapath下的META-INF/spring.factories配置文件。

  • spring.factories是java 的properties文件,配置格式是key=value的形式,只不过key和value都是java类型的完整类名。

截图看下效果:

在这里插入图片描述
从图上可以看到,org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的values是通过java的反射机制实例化为对应的@Configuration的java配置类,并汇总成一个加载到spring IOC容器中。

@ComponentScan的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
@AliasFor("basePackages")
String[] value() default {};

@AliasFor("value")
String[] basePackages() default {};

Class<?>[] basePackageClasses() default {};

Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
...
}

可以看到除了元信息,其他基本没什么了,它的作用就是自动扫描并加载符合条件的组件或bean,最后把这些bean注册到spring IOC容器中。

最后我们实现两个类,重新定位springboot的启动方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* @Description DemoConfig
* @Author YiLong Wu
* @Date 2020-03-08 12:58
* @Version 1.0.0
*/
@Configuration
@ComponentScan
@EnableAutoConfiguration
public class DemoConfig {
}


public class SpringbootDemoApplication {

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

}

显而易见,这就是一个main方法的配置类启动!


欢迎大家关注我微信公众号一起学习,探讨!
在这里插入图片描述

-------------本文结束感谢你的阅读---------