示例代码
示例代码克隆地址如下,分支为develop。
https://gitee.com/kutilion/MyArtifactForEffectiveJava.git
正文
Springboot服务启动调用了如下的run方法。 这个方法的内容比较多,分几部分来学习。今天学习标注的⑥和⑦。
⑥ 获取异常分析器
⑦ 准备上下文
public ConfigurableApplicationContext run(String... args) { ... // ⑥ exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); // ⑦ prepareContext(context, environment, listeners, applicationArguments, printedBanner); ... }
⑥获取异常分析器
getSpringFactoriesInstances可以看成sprigboot的资源加载器,从META-INF/spring.factories中加载指定的Bean。首先加载的是FailureAnalyzers,它定义在spring-boot-2.1.3.RELEASE.jar的spring.factories中。通过FailureAnalyzers类的构造方法调用SpringFactoriesLoader.loadFactoryNames再次读取spring-boot-autoconfigure-2.1.3.RELEASE.jar包以及spring-boot-2.1.3.RELEASE.jar包spring.factories中所有FailureAnalyzer的名字并且进行实例化, 实例化之后装在FailureAnalyzers的列表属性内部。
Springboot提供的异常分析器大约有17种,基本涵盖了经常出现的异常等。个人感觉这个功能主要是为了出现错误的时候分析并且跟踪。
⑦准备上下文
先看一下prepareContext方法的代码
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { // 给上下文设置环境,环境包括各种环境变量及配置 context.setEnvironment(environment); // 上下文后处理 postProcessApplicationContext(context); // 在刷新上下文之前,设定一些初始化器 applyInitializers(context); // 这个方法没有实装,可能为了扩展。 listeners.contextPrepared(context); // 启动日志 if (this.logStartupInfo) { logStartupInfo(context.getParent() == null); logStartupProfileInfo(context); } // 注册特定单例bean // Add boot specific singleton beans ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); beanFactory.registerSingleton("springApplicationArguments", applicationArguments); // 判断是否打印banner if (printedBanner != null) { beanFactory.registerSingleton("springBootBanner", printedBanner); } // 是否允许覆盖同名的单例bean,参数this.allowBeanDefinitionOverriding可以在run方法执行前设定 if (beanFactory instanceof DefaultListableBeanFactory) { ((DefaultListableBeanFactory) beanFactory) .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); // 获取资源primarySources // Load the sources Set<Object> sources = getAllSources(); Assert.notEmpty(sources, "Sources must not be empty"); // 将bean加载到上下文 load(context, sources.toArray(new Object[0])); // 向上下文中添加ApplicationListener,并广播ApplicationPreparedEvent事件 listeners.contextLoaded(context); }
setEnvironment
这个方法讲SpringApplication生成environment设定到上下文种。上一章种提过的AnnotatedBeanDefinitionReader生成的新environment也在这里被替换成SpringApplication生成的environment。原因是我们使用了Springboot,SpringApplication生成environment支持Springboot。AnnotatedBeanDefinitionReader生成是默认的环境, 无法满足要求。是为了支持不使用Springboot的项目生成的。
postProcessApplicationContext
这个方法顾名思义是上下文的后处理。但是其中的前两个if语句因判断条件this.beanNameGenerator != null和this.resourceLoader != null都是false,不起作用。第三个if判断条件this.addConversionService是true,主要是将默认的convert(如StringToTimeZoneConverter)和formater(如CurrencyUnitFormatter)都注册进来。这个addConversionService是SpringApplication类的成员变量,默认为true。
applyInitializers
初始化器,是在建立SpringApplication的时候通过扫描spring.factories添加进来的。 具体可见SpringApplication类的构造方法种调用的setInitializers方法。这里调用了所有初始化器的initialize方法进行各种功能的初始化。通过debug调试或者查看spring-boot-2.1.3.RELEASE.jar和spring-boot-autoconfiguration-2.13.RELEASE.jar包的spring.factories文件, 可以发现一共有六个初始化器会被执行。
①DelegatingApplicationContextInitializer
当环境种有属性context.initializer.classes定义的初始化器(一般是用户自定义)的时候,这个类作为这些初始化器的代理, 来执行自定义初始化器的初始化方法。
②ContextIdApplicationContextInitializer
当环境种有属性spring.application.name定义的名字的时候,取得这个值设定为application的id。如果不存在则使用默认的名字"application"。
③ConfigurationWarningsApplicationContextInitializer
向上下文中注册了一个类型为ConfigurationWarningsPostProcessor的BeanFactoryPostProcessor。BeanFactoryPostProcessor在底层bean创建之后执行一些操作。这里是底层bean注册如果出现了警告则输出这些警告。
④ServerPortInfoApplicationContextInitializer
与其说这是一个初始化器,倒不如说这是一个监听器。它的构造方法将自己作为监听器加入到了上下文中。当触发事件WebServerInitializedEvent时它将真实访问的服务器端口注册到环境的local.server.port变量中,同时也注册到上下文中。如果当前上下文有父类,也会注册到父类中。其中的server是可指定的。这样,服务器端口就可以直接注解@Value或者environment.getProperty来使用。
⑤SharedMetadataReaderFactoryContextInitializer
它注册了CachingMetadataReaderFactory并且配置了ConfigurationClassPostProcessor。
⑥ConditionEvaluationReportLoggingListener
将ConditionEvaluationReport写入到日志,使用DEBUG或者INFO级别输出。这个伪装成初始化器的监听器监听两个事件ContextRefreshedEvent以及ApplicationFailedEvent。初始化失败或者出现不可预测的运行时异常时,会输出日志。
beanFactory.registerSingleton
从控制台输入的参数中得到需要注册的单例bean并且注册。
load(context, sources.toArray(new Object[0]));
创建了一个BeanDefinitionLoader对象;BeanDefinitionLoader中有成员变量AnnotatedBeanDefinitionReader,XmlBeanDefinitionReader和ClassPathBeanDefinitionScanner,通过这三个成员变量从底层源加载bean定义。跟进的话,可以发现实际上调用了BeanDefinitionLoader类的方法private int load(Object source)。可知一共有四种资源可以被加载:Class、Resource、Package和CharSequence四种,加载方式也不相同。
因为本例中只有一个资源就是工程本身,加载的时候是通过Class方式加载的。加载之前会判断是否有@Component注解,如果有则封装成一个名字为工程名(bootApplication)的BeanDefinition对象,并将其注册到beanFactory的BeanDefinitionMap中。
listeners.contextLoaded(context);
通过单步调试会发现当前的变量listners中只有一个元素EventPublishingRunListener。并且调用了它的contextLoaded方法,为起内部的监听器传递了上下文对象。这时其内部的监听器拥有上下文的引用。之后contextLoaded方法广播了ApplicationPreparedEvent事件。这会触发所有接受这个事件的监听器的onApplicationEvent方法。默认会触发如下5个监听器,
- ConfigFileApplicationListener
- LoggingApplicationListener
- BackgroundPreinitializer
- DelegatingApplicationListener
- ApplicationPreparedEventListener
其中最后一个是示例中自定义的监听器。
总结
漫长的一章,每天花费30分钟到一小时差不多用了一个星期时间。初步了解了上下文是怎样准备的。尤其是单例bean的加载(load),详细了解才知道springboot是如何通过注解或者xml配置文件来加载bean的。
本文暂时没有评论,来添加一个吧(●'◡'●)