springboot的启动过程原理

WuYiLong原创大约 4 分钟javaspringboot

在这里插入图片描述

原始启动

@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方法的配置类启动!

上次编辑于:
贡献者: wuyilong