springboot的启动过程原理
原始启动
@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容器中,这个最重要了,后面详解。 代码如下:
@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的自身定义:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
由上我们可以看到这个也是被@Configuration标注,所以说,springboot的启动类本身也是一个bean。
@EnableAutoConfiguration的定义
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
...
}
由上可以看到这个@EnableAutoConfigurations是通过@Import的支持借助AutoConfigurationImportSelector实现自动配置的功能, 这个时候得再往下看AutoConfigurationImportSelector的定义:
/**
* 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中的方法去看下定义:
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的定义
@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的启动方式
/**
* @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方法的配置类启动!