当前位置:   article > 正文

SpringBoot启动流程源码分析SpringApplication准备阶段_configureheadlessproperty()

configureheadlessproperty()

准备阶段分析

以下先看下SpringApplication的run()方法

  1. package org.springframework.boot;
  2. public ConfigurableApplicationContext run(String... args) {
  3. //1.计时器
  4. StopWatch stopWatch = new StopWatch();
  5. stopWatch.start();
  6. ConfigurableApplicationContext context = null;
  7. Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
  8. //2.headless配置
  9. configureHeadlessProperty();
  10. //3、获取监听
  11. SpringApplicationRunListeners listeners = getRunListeners(args);
  12. listeners.starting();
  13. try {
  14. //应用程序启动的参数
  15. ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
  16. //4、准备环境
  17. ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
  18. //环境创建成功后,配置bean信息,决定是否跳过 BeanInfo 类的扫描,如果设置为 true,则跳过
  19. configureIgnoreBeanInfo(environment);
  20. //打印banner信息
  21. Banner printedBanner = printBanner(environment);
  22. context = createApplicationContext();
  23. exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
  24. new Class[] { ConfigurableApplicationContext.class }, context);
  25. prepareContext(context, environment, listeners, applicationArguments, printedBanner);
  26. refreshContext(context);
  27. afterRefresh(context, applicationArguments);
  28. //停止计时
  29. stopWatch.stop();
  30. //控制是否打印日志的,这里为true,即打印日志
  31. if (this.logStartupInfo) {
  32. new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
  33. }
  34. listeners.started(context);
  35. callRunners(context, applicationArguments);
  36. }
  37. catch (Throwable ex) {
  38. handleRunFailure(context, ex, exceptionReporters, listeners);
  39. throw new IllegalStateException(ex);
  40. }
  41. try {
  42. listeners.running(context);
  43. }
  44. catch (Throwable ex) {
  45. handleRunFailure(context, ex, exceptionReporters, null);
  46. throw new IllegalStateException(ex);
  47. }
  48. return context;
  49. }
  50. 复制代码

我将会根据执行过程逐行进行分析

1、StopWatch计时器

此类实则为计时器,如下对具体使用进行分析

  1. StopWatch stopWatch = new StopWatch();
  2. //开始计时
  3. stopWatch.start();
  4. //停止计时
  5. stopWatch.stop();
  6. 复制代码

对于具体打印的上面写的为

  1. //将当前类传入StartupInfoLogger创建了一个对象
  2. //然后调用logStarted打印日志
  3. new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
  4. //创建一个Log类
  5. protected Log getApplicationLog() {
  6. if (this.mainApplicationClass == null) {
  7. return logger;
  8. }
  9. return LogFactory.getLog(this.mainApplicationClass);
  10. }
  11. //调用log类的log.info()方法来打印日志
  12. public void logStarted(Log log, StopWatch stopWatch) {
  13. if (log.isInfoEnabled()) {
  14. log.info(getStartedMessage(stopWatch));
  15. }
  16. }
  17. //打印详细的日志
  18. private StringBuilder getStartedMessage(StopWatch stopWatch) {
  19. StringBuilder message = new StringBuilder();
  20. message.append("Started ");
  21. message.append(getApplicationName());
  22. message.append(" in ");
  23. message.append(stopWatch.getTotalTimeSeconds());
  24. try {
  25. double uptime = ManagementFactory.getRuntimeMXBean().getUptime() / 1000.0;
  26. message.append(" seconds (JVM running for " + uptime + ")");
  27. }
  28. catch (Throwable ex) {
  29. // No JVM time available
  30. }
  31. return message;
  32. }
  33. 复制代码

这里可以看到stopWatch.getTotalTimeSeconds()方法就是来获取实际的计时时间的。再者,通过这几行代码,我们也可以考虑下平常在写代码的时候,有几种日志打印方式?SpringBoot是怎么集成日志框架的?

2、configureHeadlessProperty()

  1. private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";
  2. private void configureHeadlessProperty() {
  3. System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
  4. System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
  5. }
  6. 复制代码

这一部分代码这样理解吧,首先java.awt包提供了用于创建用户界面和绘制图形图像的所有分类,那么 属性SYSTEM_PROPERTY_JAVA_AWT_HEADLESS就一定会和用户界面相关了。 这里将SYSTEM_PROPERTY_JAVA_AWT_HEADLESS设置为true,其实就是表示在缺少显示屏、键盘或者鼠标中的系统配置,如果将其设置为true,那么headless工具包就会被使用。

3、getRunListeners(args) 获取监听

总体上可以分这三步

  • 获取一个默认的加载器
  • 根据类型获取spring.factories中符合的类名
  • 创建类实例,返回

如下将跟下代码

  1. //获取所有监听
  2. SpringApplicationRunListeners listeners = getRunListeners(args);
  3. //启动监听
  4. listeners.starting();
  5. 复制代码

跳转进入getRunListeners方法

  1. private SpringApplicationRunListeners getRunListeners(String[] args) {
  2. Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
  3. return new SpringApplicationRunListeners(logger,
  4. getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
  5. }
  6. private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
  7. //3.1获取类加载器
  8. ClassLoader classLoader = getClassLoader();
  9. // Use names and ensure unique to protect against duplicates
  10. //3.2 根据类型获取spring.factories中符合的类名
  11. Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
  12. //3.3 创建类实例
  13. List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
  14. //对实例进行排序
  15. AnnotationAwareOrderComparator.sort(instances);
  16. return instances;
  17. }
  18. 复制代码

SpringApplicationRunListeners类解读

先看下SpringApplicationRunListeners类

  1. /**
  2. * A collection of {@link SpringApplicationRunListener}.
  3. *
  4. * @author Phillip Webb
  5. */
  6. class SpringApplicationRunListeners {
  7. private final Log log;
  8. private final List<SpringApplicationRunListener> listeners;
  9. SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) {
  10. this.log = log;
  11. this.listeners = new ArrayList<>(listeners);
  12. }
  13. 复制代码

SpringApplicationRunListeners类内部关联了SpringApplicationRunListener的集合,说白了就是用List集合存储了SpringApplicationRunListeners类,那么,我们就需要了解一下这个类是干嘛的

老规矩,先把源码抬上来

  1. /**
  2. *//可以理解为Spring Boot应用的运行时监听器
  3. * Listener for the {@link SpringApplication} {@code run} method.
  4. *//SpringApplicationRunListener的构造器参数必须依次为SpringApplication和String[]类型
  5. * {@link SpringApplicationRunListener}s are loaded via the {@link SpringFactoriesLoader}
  6. * and should declare a public constructor that accepts a {@link SpringApplication}
  7. * instance and a {@code String[]} of arguments.
  8. *//每次运行的时候将会创建一个 SpringApplicationRunListener
  9. A new
  10. * {@link SpringApplicationRunListener} instance will be created for each run.
  11. *
  12. */
  13. public interface SpringApplicationRunListener {
  14. /**
  15. * Called immediately when the run method has first started. Can be used for very
  16. * early initialization.
  17. */
  18. //Spring应用刚启动
  19. void starting();
  20. /**
  21. * Called once the environment has been prepared, but before the
  22. * {@link ApplicationContext} has been created.
  23. * @param environment the environment
  24. */
  25. //ConfigurableEnvironment准备妥当,允许将其调整
  26. void environmentPrepared(ConfigurableEnvironment environment);
  27. /**
  28. * Called once the {@link ApplicationContext} has been created and prepared, but
  29. * before sources have been loaded.
  30. * @param context the application context
  31. */
  32. //ConfigurableApplicationContext准备妥当,允许将其调整
  33. void contextPrepared(ConfigurableApplicationContext context);
  34. /**
  35. * Called once the application context has been loaded but before it has been
  36. * refreshed.
  37. * @param context the application context
  38. */
  39. //ConfigurableApplicationContext已装载,但是任未启动
  40. void contextLoaded(ConfigurableApplicationContext context);
  41. /**
  42. * The context has been refreshed and the application has started but
  43. * {@link CommandLineRunner CommandLineRunners} and {@link ApplicationRunner
  44. * ApplicationRunners} have not been called.
  45. * @param context the application context.
  46. * @since 2.0.0
  47. */
  48. //ConfigurableApplicationContext已启动,此时Spring Bean已初始化完成
  49. void started(ConfigurableApplicationContext context);
  50. /**
  51. * Called immediately before the run method finishes, when the application context has
  52. * been refreshed and all {@link CommandLineRunner CommandLineRunners} and
  53. * {@link ApplicationRunner ApplicationRunners} have been called.
  54. * @param context the application context.
  55. * @since 2.0.0
  56. */
  57. //Spring应用正在运行
  58. void running(ConfigurableApplicationContext context);
  59. /**
  60. * Called when a failure occurs when running the application.
  61. * @param context the application context or {@code null} if a failure occurred before
  62. * the context was created
  63. * @param exception the failure
  64. * @since 2.0.0
  65. */
  66. //Spring应用运行失败
  67. void failed(ConfigurableApplicationContext context, Throwable exception);
  68. }
  69. 复制代码

单纯的看源码,是一个简单的接口,这时候我们可以看下作者给的注释。理解部分就直接加到上面源码中了。

再看下他的实现类EventPublishingRunListener

  1. public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
  2. private final SpringApplication application;
  3. private final String[] args;
  4. private final SimpleApplicationEventMulticaster initialMulticaster;
  5. public EventPublishingRunListener(SpringApplication application, String[] args) {
  6. this.application = application;
  7. this.args = args;
  8. this.initialMulticaster = new SimpleApplicationEventMulticaster();
  9. for (ApplicationListener<?> listener : application.getListeners()) {
  10. this.initialMulticaster.addApplicationListener(listener);
  11. }
  12. }
  13. 复制代码

这里我们看到两点:

  • 构造器参数和他实现的接口(上面刚分析了)注释中规定的一致
  • 将SpringApplication中的ApplicationListener实例列表全部添加到了SimpleApplicationEventMulticaster对象中

SimpleApplicationEventMulticaster是Spring框架的一个监听类,用于发布Spring应用事件。因此EventPublishingRunListener实际充当了Spring Boot事件发布者的角色。

这里我再跟进源码的时候发现,针对SpringBoot的事件/监听机制内容还是挺多的,我们在充分理解的时候需要先了解Spring的事件/监听机制,后面将两个结合后单独进行对比分析。

3.1获取类加载器getClassLoader()

  1. ClassLoader classLoader = getClassLoader();
  2. public ClassLoader getClassLoader() {
  3. if (this.resourceLoader != null) {
  4. return this.resourceLoader.getClassLoader();
  5. }
  6. return ClassUtils.getDefaultClassLoader();
  7. }
  8. 复制代码

这里的类加载器获取首先是获取resourceLoader的类加载器,获取不到则获取默认的类加载器。 resourceLoader是资源加载器类,有具体的实现类。

3.2 根据类型获取spring.factories中符合的类名

  1. SpringFactoriesLoader.loadFactoryNames(type, classLoader)
  2. public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
  3. //获取类型名称:org.springframework.context.ApplicationContextInitializer
  4. String factoryClassName = factoryClass.getName();
  5. return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
  6. }
  7. 复制代码

我们继续对loadSpringFactories追下去

  1. private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
  2. //从缓存里面获取
  3. MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
  4. if (result != null) {
  5. return result;
  6. } else {
  7. try {
  8. //执行classLoader.getResources("META-INF/spring.factories"),表示通过加载器获取META-INF/spring.factories下的资源
  9. Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
  10. LinkedMultiValueMap result = new LinkedMultiValueMap();
  11. while(urls.hasMoreElements()) {
  12. //文件地址
  13. URL url = (URL)urls.nextElement();
  14. //从指定位置加载UrlResource
  15. UrlResource resource = new UrlResource(url);
  16. //加载里面的属性,属性见下图
  17. Properties properties = PropertiesLoaderUtils.loadProperties(resource);
  18. Iterator var6 = properties.entrySet().iterator();
  19. while(var6.hasNext()) {
  20. Entry<?, ?> entry = (Entry)var6.next();
  21. //获取key
  22. String factoryClassName = ((String)entry.getKey()).trim();
  23. //获取value
  24. String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
  25. int var10 = var9.length;
  26. //这里是将查询出来的key作为result的key,value转换成字符数组存放到result的value
  27. for(int var11 = 0; var11 < var10; ++var11) {
  28. String factoryName = var9[var11];
  29. result.add(factoryClassName, factoryName.trim());
  30. }
  31. }
  32. }
  33. //将结果集存入缓存中
  34. cache.put(classLoader, result);
  35. return result;
  36. } catch (IOException var13) {
  37. throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
  38. }
  39. }
  40. }
  41. 复制代码

  1. default V getOrDefault(Object key, V defaultValue) {
  2. V v;
  3. return (((v = get(key)) != null) || containsKey(key))
  4. ? v
  5. : defaultValue;
  6. }
  7. 复制代码

这个的意思是如果没有,则获取一个空的list

3.3创建实例createSpringFactoriesInstances()

这一步其实就是将上一步从META-INF/spring.factories加载进来的资源进行实例化。

  1. private <T> List<T> createSpringFactoriesInstances()(Class<T> type, Class<?>[] parameterTypes,
  2. ClassLoader classLoader, Object[] args, Set<String> names) {
  3. List<T> instances = new ArrayList<>(names.size());
  4. for (String name : names) {
  5. try {
  6. //根据类加载器获取指定类
  7. Class<?> instanceClass = ClassUtils.forName(name, classLoader);
  8. Assert.isAssignable(type, instanceClass);
  9. //根据参数获取构造器
  10. Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
  11. //根据传入的构造器对象以及构造器所需的参数创建一个实例
  12. T instance = (T) BeanUtils.instantiateClass(constructor, args);
  13. //添加实例到集合中
  14. instances.add(instance);
  15. }
  16. catch (Throwable ex) {
  17. throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
  18. }
  19. }
  20. return instances;
  21. }
  22. 复制代码

4、环境准备prepareEnvironment

  1. prepareEnvironment(listeners, applicationArguments)
  2. 复制代码
  1. private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
  2. ApplicationArguments applicationArguments) {
  3. // Create and configure the environment
  4. //4.1 创建一个环境
  5. ConfigurableEnvironment environment = getOrCreateEnvironment();
  6. //4.2 配置环境
  7. configureEnvironment(environment, applicationArguments.getSourceArgs());
  8. //4.3 ConfigurationPropertySourcesPropertySource对象存入到第一位
  9. ConfigurationPropertySources.attach(environment);
  10. //listeners环境准备(就是广播ApplicationEnvironmentPreparedEvent事件)
  11. listeners.environmentPrepared(environment);
  12. // 将环境绑定到SpringApplication
  13. bindToSpringApplication(environment);
  14. // 如果是非web环境,将环境转换成StandardEnvironment
  15. if (!this.isCustomEnvironment) {
  16. environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
  17. deduceEnvironmentClass());
  18. }
  19. // 配置PropertySources对它自己的递归依赖
  20. ConfigurationPropertySources.attach(environment);
  21. return environment;
  22. }
  23. 复制代码

4.1创建一个环境getOrCreateEnvironment

  1. private ConfigurableEnvironment getOrCreateEnvironment() {
  2. //有的话,直接返回
  3. if (this.environment != null) {
  4. return this.environment;
  5. }
  6. //这里我们在上面见到过,通过WebApplicationType.deduceFromClasspath()方法获取的
  7. switch (this.webApplicationType) {
  8. case SERVLET:
  9. return new StandardServletEnvironment();
  10. case REACTIVE:
  11. return new StandardReactiveWebEnvironment();
  12. default:
  13. return new StandardEnvironment();
  14. }
  15. }
  16. 复制代码

这里创建了一个StandardServletEnvironment实例的环境 systemProperties用来封装了JDK相关的信息 如下图

systemEnvironment用来封转环境相关的信息

封装的还是挺详细的哈。

4.2 配置环境

  1. protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
  2. if (this.addConversionService) {
  3. ConversionService conversionService = ApplicationConversionService.getSharedInstance();
  4. environment.setConversionService((ConfigurableConversionService) conversionService);
  5. }
  6. configurePropertySources(environment, args);
  7. configureProfiles(environment, args);
  8. }
  9. 复制代码

setConversionService(ConfigurableConversionService conversionService)方法继承于ConfigurablePropertyResolver接口, 该接口是PropertyResolver类型都将实现的配置接口。提供用于访问和自定义将属性值从一种类型转换为另一种类型时使用的ConversionService的工具。PropertyResolver是用于针对任何底层源解析属性的接口。


configurePropertySources(environment, args);当前方法主要是将启动命令中的参数和run 方法中的参数封装为PropertySource。

  1. protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
  2. //获取所有的属性源,就是获取4.1的ConfigurableEnvironment上获取到的属性
  3. MutablePropertySources sources = environment.getPropertySources();
  4. if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
  5. sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
  6. }
  7. //是否添加命令启动参数,addCommandLineProperties为true,表示需要添加,但是前提是你得配置了参数
  8. if (this.addCommandLineProperties && args.length > 0) {
  9. String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
  10. if (sources.contains(name)) {
  11. PropertySource<?> source = sources.get(name);
  12. CompositePropertySource composite = new CompositePropertySource(name);
  13. composite.addPropertySource(
  14. new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
  15. composite.addPropertySource(source);
  16. sources.replace(name, composite);
  17. }
  18. else {
  19. sources.addFirst(new SimpleCommandLinePropertySource(args));
  20. }
  21. }
  22. }
  23. 复制代码

configureProfiles(environment, args);环境配置

  1. protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
  2. //获取激活的环境
  3. environment.getActiveProfiles(); // ensure they are initialized
  4. // But these ones should go first (last wins in a property key clash)
  5. Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
  6. profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
  7. //设置当前的环境
  8. environment.setActiveProfiles(StringUtils.toStringArray(profiles));
  9. }
  10. 复制代码

4.3 ConfigurationPropertySourcesPropertySource对象存入

  1. public static void attach(Environment environment) {
  2. Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
  3. //获取所有的属性源,就是获取4.1的ConfigurableEnvironment上获取到的属性
  4. MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources();
  5. //判断是否有 属性 configurationProperties
  6. PropertySource<?> attached = sources.get(ATTACHED_PROPERTY_SOURCE_NAME);
  7. if (attached != null && attached.getSource() != sources) {
  8. sources.remove(ATTACHED_PROPERTY_SOURCE_NAME);
  9. attached = null;
  10. }
  11. if (attached == null) {
  12. //sources封装成ConfigurationPropertySourcesPropertySource对象,并把这个对象放到sources的第一位置
  13. sources.addFirst(new ConfigurationPropertySourcesPropertySource(ATTACHED_PROPERTY_SOURCE_NAME,
  14. new SpringConfigurationPropertySources(sources)));
  15. }
  16. }
  17. 复制代码

总结

准备阶段主要干了如下几件事情

  • 设置headless为true(表示可以在缺少显示屏、键盘或者鼠标时候的系统配置)

  • 文件META-INF\spring.factories中获取SpringApplicationRunListener接口的实现类EventPublishingRunListener,主要发布SpringApplicationEvent。

  • 创建Environment并设置比如环境信息,系统属性,输入参数和profile等信息

  • 打印Banner信息


本文仅为个人能力范围内理解,旨在分享出来和大家讨论技术,共同努力,共同进步!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/319732
推荐阅读
相关标签
  

闽ICP备14008679号