Springboot启动分析

Springboot启动分析

建议先看Spring-@ComponentScan分析
SpringBoot的例子就不写了,最熟悉的代码如下:

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

SpringApplication的作用是什么?@SpringBootApplication注解有什么功能?Springboot怎么做自动装配是怎么做的?这篇文章就回答了相关的问题。

SpringApplication

作用

通过它来创建加载SpringApplication

在Spring的时候知道,得先创建Application,在启动的得把配置类,或者指定扫描的包,这类的定义信息告诉Spring,它才可以启动。比如下面的代码

	public class TestAopApplication {
	public static void main(String[] args) {
		try {
			AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
            // 告诉扫描的路径
			context.scan(TestAopApplication.class.getPackage().getName());
            // 显性调用refresh方法,启动容器
			context.refresh();
			TestBean bean = context.getBean(TestBean.class);
			bean.sayHello();
		}catch (Exception e){
			e.printStackTrace();
		}
	}
}

那么对于SpringApplication来说,它也是上面类似的逻辑,创建Application,创建好Environment,在增加一系列的监听方法,比如application创建好了的回调,环境创建好了的回调等等,通过这些监听就可以进一步的定制化对应的对象。

上面说的只是一个思想,实际上它大体的做法是,创建一个BootStrapApplication用来它引导创建ApplicationContext和Environment,并且创建一直到启动环节中,都有对应的监听方法,来对增强操作(比如,日志的选择,配置文件的解析(Application.yaml)等等),此外还会设置创建好的ApplicationContext加载资源的路径,设置一些属性(增加BeanFactoryPostProcess)。在调用ConfigurableApplicationContext#refresh()方法之前,会将BootStrapApplication关闭掉。在容器跑起来之后,会发布事件,还会调用ApplicationRunnerCommandLineRunner

详细

继续上面的说,在SpringApplication类里面利用BootStrap来创建Application的时候,会有一系列的监听类。但是这些监听类要怎么创建呢?

  • 代码里面写死,new出来,放里面去。(但这很不好,作为一个框架来说,没有拓展性了)。
  • 像Spring那样,搞一个注解扫描。(不可取,想想Spring就知道,那一套得多烦。作为一个引导程序没必要这么搞)
  • 写成配置文件,全类名。读取配置文件,实例化它。(SPI)。
    • SpringBoot采取的就是这个。只不过和Java标准的SPI不一样,它指定文件名称,文件的key是要实现接口的全类名,v是具体的实现类的全限定类名,多个实现用逗号分开。这个文件叫做META-INF/spring.factories

有这样的机制,如果想要那个接口需要拓展,直接写在里面,并且会将之前已经加载好的配置文件缓存起来,用的时候获取一下,并且实例化,就好了。回到Springboot中,看看那些接口需要拓展。

  • BootstrapRegistryInitializer (他是在BootstrapRegistry创建好了之后,在使用之前的一个初始化回调)

  • ApplicationContextInitializer (在refresh方法之前的回调,用来初始化ApplicationContext)

  • ApplicationListener(这是典型的Spring里面的事件监听,指定事件来监听)

  • SpringApplicationRunListener(在SpringBoot启动的时候提供的监听类,在各个环节都有方法)。

    需要注意的是,它的实现类在Springboot中就一个 EventPublishingRunListener,但是EventPublishingRunListener里面聚合了SimpleApplicationEventMulticaster,通过它来做发布事件,会在创建EventPublishingRunListener的时候将ApplicationListener传递给SimpleApplicationEventMulticaster。

    一般来说,这个玩意基本不会动的。

  • SpringBootExceptionReporter(在启动报错的时候回调)

  • EnvironmentPostProcessor

    这个是重点:

    ​ 它是做Environment对象的增强处理的。具体做法如下:

    EnvironmentPostProcessorApplicationListener监听了它感兴趣的事件,SpringApplication创建好Environment之后,会通过EventPublishingRunListener发布对应的事件,EnvironmentPostProcessorApplicationListener来处理,它再从处理的时候又会从spring.factories中加载EnvironmentPostProcessor的实现类,来做Environment的后置处理。其中就包括解析Application.yaml文件

  • AutoConfigurationImportListener(在自动配置类导入的时候回调)

  • EnableAutoConfiguration(自动配置类)

    这个是重点:

    ​ 还记得之前说配置类中@Import注解嘛?有两个特殊的类,ImportSelectorImportBeanDefinitionRegistrar。其中ImportSelector可以返回一个String数组,它里面包含的是类的全类名,Spring拿到这个之后,会加载对应类,继续解析一遍。这其实就是自动装配,这个功能不是Springboot搞出来的,之前就存在这样的支持。

    ​ 那么key是EnableAutoConfiguration,value是对应的实现类,用逗号分隔,在ImportSelector的实现类里面从spring.factories里面读取到,返回给Spring,Spring就会继续把他们解析一遍。可以在自动配置类上面写那些之前在@Configuration类上面写的注解,Spring会解析他们,将Bean注册到Spring中。

    … 因为涉及的地方太多了,这里就列举几个有经典的。

加载 META-INF/spring.factories的代码长什么样(怎么加载的)

对应的代码在 SpringFactoriesLoader#loadFactories(Class , @Nullable ClassLoader )

这里的代码不难理解,总体就是利用classLoader.getResource加载配置文件,读取配置文件,将v逗号分割。

	public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
		Assert.notNull(factoryType, "'factoryType' must not be null");
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
        // 通过classload和指定需要的lei找到对应的实现类的集合了,
		List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
		if (logger.isTraceEnabled()) {
			logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
		}
		List<T> result = new ArrayList<>(factoryImplementationNames.size());
		for (String factoryImplementationName : factoryImplementationNames) {
            // 实例化
			result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
		}
        // order接口排序
		AnnotationAwareOrderComparator.sort(result);
		return result;
	}
   // 真正做加载的地方
	private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        // 先从缓存中拿,缓存的key是对应的classloader,v是属于它的数据
		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);
		}
	}

当然,SpringApplication绝不是我说的这么简单,这里只是说了一些大体的思路,具体的实现的细节这里不展开说了

@SpringBootApplication

他是一个复合注解,

Springboot启动分析_第1张图片

而它下面的属性对应的都是元注解里面的属性。主要分析如下:

@SpringBootConfiguration

Springboot启动分析_第2张图片

就是一个配置类。没有什么可说的了。

@EnableAutoConfiguration

Springboot启动分析_第3张图片

可以看到它导入了一个ImportSelector,可以用它来读取spring.factories里面的自动装配的全类名,经过处理返回给Spring来解析。处理其实就是它的属性excludeName了,拿到所有的自动装配的类的全类名之后,从集合中将指定的移除就好了,所以,上面的两个属性是一个意思,并且exclude最后还是要变为String的。调用的是Class.getName()。

此外,还可以在这里可以做一些Condition的判断,比如AutoConfigureAfter,AutoConfigureBefore,关于这个判断,之后专门说说

还得注意,还有一个注解AutoConfigurationPackage

AutoConfigurationPackage

Springboot启动分析_第4张图片

他会往Spring容器中注册一个BasePackages,bean的名字叫做 AutoConfigurationPackages.class.getName(),它里面保存了 属性所指定的包名,如果属性为空,就是当前注解所在的类。放在容器里面之后就可以通过AutoConfigurationPackages来操作,存储自动注入的包是为了延迟引用,比如说JPA的实体扫描。

Springboot启动分析_第5张图片

@ComponentScan

这注解之前已经分析过了,相当于调用context.scan(String);方法,但是它搞了两个过滤器。还都是excludeFilters

  1. TypeExcludeFilter

    可以从Spring容器里面获取TypeExcludeFilter的实现类,循环调用。

  2. AutoConfigurationExcludeFilter

    在做@ComponentScan的时候排除自动配置类

Springboot启动分析_第6张图片

详细的SpringApplication类的运行过程,就不展开说了,Springboot的自动装配和启动就说到这里了。


关于博客这件事,我是把它当做我的笔记,里面有很多的内容反映了我思考的过程,因为思维有限,不免有些内容有出入,如果有问题,欢迎指出。一同探讨。谢谢。

你可能感兴趣的:(spring,spring,boot,java,spring)