springboot情操陶冶-SpringApplication(二)
承接前文springboot情操陶冶-SpringApplication(一),本文将对run()方法作下详细的解析
SpringApplication#run()
main函数经常调用的run()方法是我们分析的关键,先上源码
public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); // 读取java.awt.headless系统变量,默认为true.常用于linux图片的渲染 configureHeadlessProperty(); // 获取SpringApplicationRunListener接口集合并实例化调用公共接口starting() SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); // environment configuration ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); // spring.beaninfo.ignore属性读取 configureIgnoreBeanInfo(environment); // springboot的banner样图 Banner printedBanner = printBanner(environment); // 创建spring应用上下文(尚未刷新) context = createApplicationContext(); // SpringBootExceptionReporter接口集合读取 exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); // applicationContext configuration prepareContext(context, environment, listeners, applicationArguments, printedBanner); // 刷新spring应用上下文 refreshContext(context); afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } // SpringApplicationRunListener接口的started()方法调用 listeners.started(context); callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { // SpringApplicationRunListener接口的running()方法调用 listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context; }
以上的代码注释有点多,笔者分块来进行罗列分析
SpringApplication#getRunListeners()
获取SpringApplicationRunListener接口集合并实例化,根据前文得知,读取的是META\spring.factories文件中的对应属性,
此处笔者以org.springframework.boot.context.event.EventPublishingRunListener为例。
先观察下其构造函数
public EventPublishingRunListener(SpringApplication application, String[] args) { this.application = application; this.args = args; this.initialMulticaster = new SimpleApplicationEventMulticaster(); for (ApplicationListener<?> listener : application.getListeners()) { this.initialMulticaster.addApplicationListener(listener); } }
注意application.getListeners()方法,根据前文得知,其会拿到类型为ApplicationListener的集合并存入至SimpleApplicationEventMulticaster广播类中;
其余方法,比如starting()/started()/environmentPrepared()等等方法均是由其统一调用所有的ApplicationListener接口的对应事件。笔者此处以starting()为例
@Override public void starting() { this.initialMulticaster.multicastEvent( new ApplicationStartingEvent(this.application, this.args)); }
其会找寻支持响应ApplicationStartingEvent事件的Listeners,并执行相应的事件方法。响应的监听器有LoggingApplicationListener和LiquibaseServiceLocatorApplicationListener等
SpringApplication#prepareEnvironment()
Environment环境准备工作
private ConfigurableEnvironment prepareEnvironment( SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { // Create and configure the environment ConfigurableEnvironment environment = getOrCreateEnvironment(); // 解析args参数和spring.profiles.active配置读取 configureEnvironment(environment, applicationArguments.getSourceArgs()); // 触发ApplicationEnvironmentPreparedEvent事件 listeners.environmentPrepared(environment); bindToSpringApplication(environment); if (this.webApplicationType == WebApplicationType.NONE) { environment = new EnvironmentConverter(getClassLoader()) .convertToStandardEnvironmentIfNecessary(environment); } ConfigurationPropertySources.attach(environment); return environment; }
内含代码内容过多,笔者此处针对自己的阅读作下小结
args参数是会被包装为SimpleCommandLinePropertySource属性源,对应key为
commandLineArgs
spring.profiles.active
系统属性读取,用于不同条件的配置ApplicationEnvironmentPreparedEvent事件触发,主要的有ConfigFileApplicationListener监听类(其会默认读取application.properties/application.xml/application.yaml配置文件)
PS:ConfigFileApplicationListener这个类比较重要,其也会去读取配置文件中的
spring.profiles.active
属性加载Profile;
另外也去读取EnvironmentPostProcessor接口来统一调用。有兴趣的读者可好好分析一下将Environment含有的属性源绑定至key为
configurationProperties
的PropertySources类型中,方便springboot全局搜索上下文所含有的属性
SpringApplication#configureIgnoreBeanInfo()
读取spring.beaninfo.ignore
属性,默认为true,主要是用于忽略bean的基本信息
SpringApplication#printBanner()
创建banner打印对象,其默认打印的样图如下(作用于console)。具体的读者感兴趣可自行阅读原理
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.0.3.RELEASE)
SpringApplication#createApplicationContext()
创建应用上下文对象,其会根据判断出来的应用类型来创建相应的上下文。
应用类型 | 上下文对应class类 |
---|---|
SERVLET | org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext |
SERVLET | org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext |
REACTIVE | org.springframework.boot.web.servlet.context.AnnotationConfigReactiveWebServerApplicationContext |
NONE | org.springframework.context.annotation.AnnotationConfigApplicationContext |
很明显,全部会应用注解方式来加载上下文。
SpringApplication#prepareContext()
对已创建的上下文对象作下预备工作
SpringApplication#applyInitializers()
启动相应的初始化类,这些初始化类均是ApplicationContextInitializer接口的实现类
protected void applyInitializers(ConfigurableApplicationContext context) { for (ApplicationContextInitializer initializer : getInitializers()) { // 检查相应的ApplicationContextInitializer实体类所接收的泛型实体class Class<?> requiredType = GenericTypeResolver.resolveTypeArgument( initializer.getClass(), ApplicationContextInitializer.class); // 只对接收的泛型为ConfigurableApplicationContext.class进行相应的初始化 Assert.isInstanceOf(requiredType, context, "Unable to call initializer."); initializer.initialize(context); } }
针对上面的代码注释,初始化类基本有ConfigurationWarningsApplicationContextInitializer/ContextIdApplicationContextInitializer/DelegatingApplicationContextInitializer/ServerPortInfoApplicationContextInitializer等。
有兴趣的可自行分析相应的初始化类都执行了哪些操作
SpringApplication#load()
加载beans到相应的上下文对象ApplicationContext中
// 此处的source一般为main函数所在的class,也可通过SpringApplication#setSource()来新增 protected void load(ApplicationContext context, Object[] sources) { if (logger.isDebugEnabled()) { logger.debug( "Loading source " + StringUtils.arrayToCommaDelimitedString(sources)); } BeanDefinitionLoader loader = createBeanDefinitionLoader( getBeanDefinitionRegistry(context), sources); if (this.beanNameGenerator != null) { loader.setBeanNameGenerator(this.beanNameGenerator); } if (this.resourceLoader != null) { loader.setResourceLoader(this.resourceLoader); } if (this.environment != null) { loader.setEnvironment(this.environment); } // 加载beans loader.load(); }
笔者此处最关心这个BeanDefinitionLoader会耍什么花样,继续往下
先从构造函数开始
BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) { Assert.notNull(registry, "Registry must not be null"); Assert.notEmpty(sources, "Sources must not be empty"); this.sources = sources; // 注解解析类 this.annotatedReader = new AnnotatedBeanDefinitionReader(registry); // xml解析类 this.xmlReader = new XmlBeanDefinitionReader(registry); if (isGroovyPresent()) { this.groovyReader = new GroovyBeanDefinitionReader(registry); } // classpath扫描类,并屏蔽指定的sources类 this.scanner = new ClassPathBeanDefinitionScanner(registry); this.scanner.addExcludeFilter(new ClassExcludeFilter(sources)); }
AnnotatedBeanDefinitionReader注解解析类,其会注册多个postProcessor接口供后续的上下文刷新操作被调用解析
@Configuration/@Autowired/@Required/
。AnnotatedBeanDefinitionReaderXML解析类,其会解析XML配置
ClassPathBeanDefinitionScanner扫描classpath环境下指定的包以及指定class类
再看下处理方法load()
public int load() { int count = 0; for (Object source : this.sources) { count += load(source); } return count; } private int load(Object source) { Assert.notNull(source, "Source must not be null"); if (source instanceof Class<?>) { return load((Class<?>) source); } if (source instanceof Resource) { return load((Resource) source); } if (source instanceof Package) { return load((Package) source); } if (source instanceof CharSequence) { return load((CharSequence) source); } throw new IllegalArgumentException("Invalid source type " + source.getClass()); } // 笔者重点关注此处 private int load(Class<?> source) { if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) { // Any GroovyLoaders added in beans{} DSL can contribute beans here GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class); load(loader); } // 查看class是否被@Component修饰过。此处多用于加载main类 if (isComponent(source)) { // 注册bean this.annotatedReaAnnotatedBeanDefinitionReaderder.register(source); return 1; } return 0; }
此处的load()方法主要是对设置的sources类进行判断是否被@Component
注解,是则注入至bean工厂。即我们常写的main()函数类一般会加上@SpringApplication
注解,其最终会被AnnotatedBeanDefinitionReader处理。
SpringApplication#refresh()
刷新上下文对象,笔者此处就不展开了,可参考之前的文章Spring源码情操陶冶-AbstractApplicationContext
异常处理
springboot的异常处理机制是通过读取SpringBootExceptionReporter接口类来对不同的异常进行不同的输出,感兴趣的可自行阅读
小结
通过上述的分析,基本对springboot的工作原理有了一定的了解,最主要的其实还是其会将公共的配置放置于META\spring.factories文件中,我们以后只要多关注此文件就会明白的更多。
至于@SpringApplication
注解是如何为springboot服务的,笔者后续再分析
作者:南柯问天 出处:http://www.cnblogs.com/question-sky/ 本文版权归本人和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
原文出处:https://www.cnblogs.com/question-sky/p/9372997.html
共同学习,写下你的评论
评论加载中...
作者其他优质文章