深入理解SpringBoot(一)----SpringBoot的启动流程分析

打印 上一主题 下一主题

主题 510|帖子 510|积分 1530

1、SpringApplication 对象实例化

   SpringApplication 文件
  
  1. public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
  2.       // 传递的source其实就是类Bootstrap
  3.     return new SpringApplication(sources).run(args);
  4.     // 实例化一个SpringApplication对象执行run方法
  5. }
复制代码
  实例化的时候又会实行initialize 方法
  
  1. private void initialize(Object[] sources) {
  2.       // 这个source依旧是上文说的Bootstrap.class 类
  3.     if (sources != null && sources.length > 0) {
  4.         this.sources.addAll(Arrays.asList(sources));
  5.         // 添加到source资源列表里面去
  6.     }
  7.     this.webEnvironment = deduceWebEnvironment();
  8.     // 设置其是否为web环境
  9.     setInitializers((Collection) getSpringFactoriesInstances(
  10.             ApplicationContextInitializer.class));
  11.     setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  12.     // 拆分为两步,一步是getSpringFactoriesInstances,再者就是set操作
  13.     // set操作很简单,就是设置当前对象的初始化对象以及监听器
  14.     this.mainApplicationClass = deduceMainApplicationClass();
  15.     // 通过堆栈信息,推断 main方法的类对象为当前的主程序类
  16. }
  17. private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
  18.             "org.springframework.web.context.ConfigurableWebApplicationContext" };
  19. private boolean deduceWebEnvironment() {
  20.     for (String className : WEB_ENVIRONMENT_CLASSES) {
  21.            // 遍历包含上述两个类名称的数组
  22.         if (!ClassUtils.isPresent(className, null)) {
  23.                // 一旦发现不存在该类,就立即返回 deduce 推断不是web环境
  24.             return false;
  25.         }
  26.     }
  27.     // 必须同时包含两个类,才推断出为web环境
  28.     return true;
  29. }
复制代码
  getSpringFactoriesInstances 方法操作
  
  1. private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
  2.         Class<?>[] parameterTypes, Object... args) {
  3.         // 传递的type就是上面说的ApplicationContextInitializer.class以及ApplicationListener.class类
  4.         // 类型以及参数目前都没有具体指
  5.     ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
  6.     // Use names and ensure unique to protect against duplicates
  7.     Set<String> names = new LinkedHashSet<String>(
  8.             SpringFactoriesLoader.loadFactoryNames(type, classLoader));
  9.             // 通过SpringFactoriesLoader 获取对应的名称,具体详情可以看下面的代码块
  10.             // 这点需要重点关注下!!!
  11.             // 结果就是返回一个set集合
  12.     List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
  13.             classLoader, args, names);
  14.             // 看样子就是创建一个实例的集合
  15.     AnnotationAwareOrderComparator.sort(instances);
  16.     // 然后通过AnnotationAwareOrderComparator 的排序规则跪实例集合进行排序
  17.     // 排序就是看是否存在Order或者Priority注解,然后取得注解的值,排在集合前面
  18.     return instances;
  19. }
  20. private <T> List<T> createSpringFactoriesInstances(Class<T> type,
  21.         Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
  22.         Set<String> names) {
  23.     List<T> instances = new ArrayList<T>(names.size());
  24.     for (String name : names) {
  25.            // 遍历上面取到的name 集合
  26.         try {
  27.             Class<?> instanceClass = ClassUtils.forName(name, classLoader);
  28.             // 取到这个类名称的类
  29.             Assert.isAssignable(type, instanceClass);
  30.             Constructor<?> constructor = instanceClass
  31.                     .getDeclaredConstructor(parameterTypes);
  32.             // 获取当前类的符合当前参数的构造器
  33.             T instance = (T) BeanUtils.instantiateClass(constructor, args);
  34.             // 利用反射的方式生成具体的对象
  35.             instances.add(instance);
  36.         }
  37.         catch (Throwable ex) {
  38.             throw new IllegalArgumentException(
  39.                     "Cannot instantiate " + type + " : " + name, ex);
  40.         }
  41.     }
  42.     // 最后生成name映射的实例集合
  43.     return instances;
  44. }
复制代码
  SpringFactoriesLoader.loadFactoryNames 方法
  
  1. public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
  2. public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
  3.     // 传递的factoryClass 就是上面的ApplicationContextInitializer、ApplicationListener.等
  4.     String factoryClassName = factoryClass.getName();
  5.     // 获取类的全名称
  6.     try {
  7.         Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
  8.                 ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
  9.         // 如果类加载器为null,则使用系统默认的方法,否则使用当前传递的类加载器读取
  10.         // 当前类加载器可以获取到的所有文件路径为“META-INF/spring.factories” 的地址
  11.         
  12.         List<String> result = new ArrayList<String>();
  13.         while (urls.hasMoreElements()) {
  14.               // 迭代遍历url
  15.             URL url = urls.nextElement();
  16.             Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
  17.             // 读取映射的spring.factories 文件的KV键值对,存放到properties对象中
  18.             String factoryClassNames = properties.getProperty(factoryClassName);
  19.             // 类似于map一般,获取对应的值
  20.             result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
  21.             // 对值使用逗号分隔,生成list,然后去重添加到result
  22.         }
  23.         
  24.         // 总结下来就是遍历当前类环境中的所有路径为“META-INF/spring.factories”的文件
  25.         // 读取文件,然后获取k为当前类名称的所有值,然后存储到set中返回
  26.         return result;
  27.     }
  28.     catch (IOException ex) {
  29.         throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
  30.                 "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
  31.     }
  32. }
复制代码
      到这里整个的initialize操作就已经清楚了,通过类加载器可获取的全部为“META-INF/spring.factories” 的地址的文件内容,然后获取key为ApplicationContextInitializer.class以及ApplicationListener.class的类名称的值集合
然后依次就行实例化,最后排序返回,最后保存到当前对象的初始化集合以及监听器集合中,便于后续操作
需要注意到SpringFactoriesLoader.loadFactoryNames 后面很多地方都需要使用该方法去获取干系内容
当然现在只是完成了SpringApplication构造器内里的方法,还剩下后面的run(args)方法实行
如下代码块就是SpringBoot的实行过程(最后的套路依旧是Spring Framework的实行战略)
          使用SPI机制扫描 META-INF/spring.factories 这个文件,而且加载 ApplicationContextInitializer、ApplicationListener 接口实例。
1、ApplicationContextInitializer 这个类当springboot上下文Context初始化完成后会调用
2、ApplicationListener 当springboot启动时事件change后都会触发
          总结:上面就是SpringApplication初始化的代码,new SpringApplication()没做啥事情 ,使用SPI机制主要加载了META-INF/spring.factories 下面界说的事件监听器接口实现类
      2、SpringApplication的run方法启动

  
  1. public ConfigurableApplicationContext run(String... args) {
  2.     // 这是个计时器
  3.     StopWatch stopWatch = new StopWatch();
  4.     stopWatch.start();
  5.     // 记录当前服务开始启动
  6.     ConfigurableApplicationContext context = null;
  7.     // 上下文context,非常关键
  8.     FailureAnalyzers analyzers = null;
  9.     configureHeadlessProperty();
  10.     // 给系统设置headless属性值,就是设置了一些环境变量
  11.     SpringApplicationRunListeners listeners = getRunListeners(args);
  12.     // 获取事件监听器SpringApplicationRunListener类型,并且执行starting()方法
  13.     // 就是通过SpringFactoriesLoader 获取到所有SpringApplicationRunListener.class的对象
  14.     // 其中args是用来进行实例化SpringApplicationRunListener对应的对象的构造器参数
  15.     // 最后返回listener是整个系统的监听器
  16.     listeners.starting();
  17.     // 监听器开始执行
  18.     try {
  19.         ApplicationArguments applicationArguments = new DefaultApplicationArguments(
  20.                 args);
  21.         // 默认程序参数
  22.         ConfigurableEnvironment environment = prepareEnvironment(listeners,
  23.                 applicationArguments);
  24.         // 准备运行的环境上下文
  25.         Banner printedBanner = printBanner(environment);
  26.         // 打印banner,默认输出当前springboot版本等内容,可以自定义设置文本或者图片
  27.         // 具体看下面的方法详解
  28.         context = createApplicationContext();
  29.         // 创建SpringBoot最重要的上下文容器
  30.         analyzers = new FailureAnalyzers(context);
  31.         // 分析上下文出现问题的点,便于使用者可以直观的发现问题出现在哪里
  32.         // 其实套路类似,就是使用SpringFactoriesLoader获取所有的FailureAnalyzer实例对象,然后设置其bean工厂为context的bean工厂上下文
  33.         prepareContext(context, environment, listeners, applicationArguments,
  34.                 printedBanner);
  35.         // 看名称就是对context的前置准备工作,细节在后面说
  36.         refreshContext(context);
  37.         // 切入到spring framework的方式去完成context内容的装载
  38.         // 如果需要注册终止钩子,则注册一个
  39.         afterRefresh(context, applicationArguments);
  40.         // 基本上认为springboot所需的服务都加载完成,进行最后的处理操作
  41.         // 里面常用的就是CommandLineRunner
  42.         listeners.finished(context, null);
  43.         // 监听器的启动结束事件,
  44.         stopWatch.stop();
  45.         // 表示SpringBoot服务启动步骤完成,统计下启动时间等操作
  46.         if (this.logStartupInfo) {
  47.             new StartupInfoLogger(this.mainApplicationClass)
  48.                     .logStarted(getApplicationLog(), stopWatch);
  49.               // 打印SpringBoot启动成功的消息,例如 Started xxx in 12.4 seconds 等信息
  50.         }
  51.         return context;
  52.     }
  53.     catch (Throwable ex) {
  54.         handleRunFailure(context, listeners, analyzers, ex);
  55.         // 启动失败了就会输出Application startup failed 日志
  56.         // 并且会输出具体的错误内容信息
  57.         throw new IllegalStateException(ex);
  58.     }
  59. }
  60. private ConfigurableEnvironment prepareEnvironment(
  61.         SpringApplicationRunListeners listeners,
  62.         ApplicationArguments applicationArguments) {
  63.     // Create and configure the environment
  64.     ConfigurableEnvironment environment = getOrCreateEnvironment();
  65.     // 如果当前环境值不为null,直接返回
  66.     // 否则根据上文推断出的webEnvironment boolean 值 生成对象的环境对象
  67.     // 当为true的时候,生成StandardServletEnvironment
  68.     // 否则生成的是StandardEnvironment
  69.     configureEnvironment(environment, applicationArguments.getSourceArgs());
  70.     listeners.environmentPrepared(environment);
  71.     if (!this.webEnvironment) {
  72.           // 如果不是web的环境,再对当前的环境进行包装,生成一个新的运行环境对象
  73.         environment = new EnvironmentConverter(getClassLoader())
  74.                 .convertToStandardEnvironmentIfNecessary(environment);
  75.     }
  76.     return environment;
  77. }
  78. private Banner printBanner(ConfigurableEnvironment environment) {
  79.      // 参数environment就是上面生成的环境对象
  80.     if (this.bannerMode == Banner.Mode.OFF) {
  81.           // 如果设置了banner关闭模式,则不进行打印输出操作
  82.         return null;
  83.     }
  84.     ResourceLoader resourceLoader = this.resourceLoader != null ? this.resourceLoader
  85.             : new DefaultResourceLoader(getClassLoader());
  86.     // 资源加载器生成
  87.     SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(
  88.             resourceLoader, this.banner);
  89.     // 后续使用SpringApplicationBannerPrinter 类的print进行输出操作
  90.     if (this.bannerMode == Mode.LOG) {
  91.           // 打印模式,如果是log则输出到log中,否则输出到终端中
  92.         return bannerPrinter.print(environment, this.mainApplicationClass, logger);
  93.     }
  94.     return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
  95.     // 大致操作就是先看是否存在自定义的图片类型或者文字类型 banner,如果有就优先确定banner对象
  96.     // 否则就默认使用SpringBootBanner的banner(这个里面就包含了常规的springboot输出内容)
  97.     // 然后解析banner的资源,得出将要输出的字符串内容(利用日志直接输出),存储到PrintedBanner
  98. }
  99. public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."
  100.         + "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";
  101. public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
  102.         + "annotation.AnnotationConfigApplicationContext";
  103. protected ConfigurableApplicationContext createApplicationContext() {
  104.     Class<?> contextClass = this.applicationContextClass;
  105.     if (contextClass == null) {
  106.         try {
  107.             contextClass = Class.forName(this.webEnvironment
  108.                     ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
  109.             // 如果是web环境,则使用AnnotationConfigEmbeddedWebApplicationContext
  110.             // 否则就使用AnnotationConfigApplicationContext
  111.         }
  112.         catch (ClassNotFoundException ex) {
  113.             throw new IllegalStateException(
  114.                     "Unable create a default ApplicationContext, "
  115.                             + "please specify an ApplicationContextClass",
  116.                     ex);
  117.         }
  118.     }
  119.     return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
  120.     // 直接通过类,反射生成无构造参数的对象,一般情况就是AnnotationConfigEmbeddedWebApplicationContext对象了
  121. }
  122. private void prepareContext(ConfigurableApplicationContext context,
  123.         ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
  124.         ApplicationArguments applicationArguments, Banner printedBanner) {
  125.     // 传递上下文、环境、上下文参数等数据
  126.     context.setEnvironment(environment);
  127.     postProcessApplicationContext(context);
  128.     // 前置处理context上下文,包含了beanNameGenerator和resourceLoader
  129.     // 其中beanNameGenerator 可以自定义规则约定bean的名称功能
  130.     applyInitializers(context);
  131.     // 应用ApplicationContextInitializer去初始化完成对context的操作
  132.     // 具体的ApplicationContextInitializer对象就是在SpringApplication对象的构造方法中实例化创建的
  133.     // 可以给context添加额外的操作,同时也可以很方便的自定义完成自己需要的功能
  134.     listeners.contextPrepared(context);
  135.     // 执行contextPrepared 上下文准备工作的事件
  136.     if (this.logStartupInfo) {
  137.            // 日志启动标志位,默认为true
  138.         logStartupInfo(context.getParent() == null);
  139.         logStartupProfileInfo(context);
  140.         // 明确当前执行的主函数log,输出SpringBoot的开始启动信息
  141.     }
  142.     // 注册springApplicationArguments 这个bean到context中去
  143.     context.getBeanFactory().registerSingleton("springApplicationArguments",
  144.             applicationArguments);
  145.     if (printedBanner != null) {
  146.         context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
  147.         // 同样是注册,打印早就完成了
  148.     }
  149.     // Load the sources
  150.     Set<Object> sources = getSources();
  151.     // 一般情况下这个source就是SpringBoot 启动的主类Class,注意不是实例对象
  152.     Assert.notEmpty(sources, "Sources must not be empty");
  153.     load(context, sources.toArray(new Object[sources.size()]));
  154.     // 把source也就是主类当做bean,加载到spring的容器中
  155.     listeners.contextLoaded(context);
  156.     // 监听器的上下文导入完成事件 执行
  157. }
  158. private void callRunners(ApplicationContext context, ApplicationArguments args) {
  159.     List<Object> runners = new ArrayList<Object>();
  160.     runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
  161.     runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
  162.     AnnotationAwareOrderComparator.sort(runners);
  163.     // 从context获取ApplicationRunner和CommandLineRunner 对象
  164.     // 然后按照对应的规则进行排序
  165.     for (Object runner : new LinkedHashSet<Object>(runners)) {
  166.         if (runner instanceof ApplicationRunner) {
  167.             callRunner((ApplicationRunner) runner, args);
  168.         }
  169.         if (runner instanceof CommandLineRunner) {
  170.             callRunner((CommandLineRunner) runner, args);
  171.         }
  172.         // 分别执行各自的run方法
  173.     }
  174.     // 一般情况,我们如果需要在SpringBoot加载完成后需要完成一些自定义操作就是注册
  175.     // ApplicationRunner或者CommandLineRunner 的bean对象,然后自定义实现run方法即可
  176. }
复制代码
  3、总结

   就SpringBoot的启动整个过程而已,照旧很清晰的,SpringBoot的套用SpringFramework的机制,为我们自界说实现功能提供了很好的便利,整个的SpringBoot就是重新包装了一个SpringFramework。
1、new了一个SpringApplication对象,使用SPI技术加载加载 ApplicationContextInitializer、ApplicationListener 接口实例
   2、调用SpringApplication.run() 方法
   3、调用createApplicationContext()方法创建上下文对象,创建上下文对象同时会注册spring的焦点组件类(ConfigurationClassPostProcessor 、AutowiredAnnotationBeanPostProcessor 等)。
   4、调用refreshContext() 方法启动Spring容器和内置的Servlet容器(tomcat),内置的Servlet容器就是在onRefresh() 方法内里启动的
   
   引用(本文章只供本人学习以及学习的记录,如有侵权,请接洽我删除)

   SpringBoot 启动过程源码分析
SpringBoot启动流程总结
       最后编辑于:2024-09-09 20:07:32       ©    著作权归作者全部,转载或内容合作请接洽作者     

喜欢的朋侪记得点赞、收藏、关注哦!!!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

风雨同行

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表