Spring-boot---SpringApplication准备阶段的加载(一)

学习笔记是学习了 小马哥在慕课网的 《Spring Boot 2.0深度实践之核心技术篇》根据自己的需要和理解做的笔记。

前言

Spring-boot 是基于Spring构建的,也就是说Spring Framework 是学好Spring-boot的基础。

首先理解Spring-boot中的SpringApplication 引导类的前提是要简单理解Spring Framework的基础技术。

Spring Framework基础技术包括以下几点,先列出来 用于我以后复习以及总结

  • Spring 模式注解 ✔
  • Spring BeanFactory
  • Spring 应用上下文 ✔
  • Spring Bean的加载 、生命周期
  • Spring 工厂加载机制 ✔
  • Spring 应用上下文功能扩展 ✔
  • IOC(控制反转)& DI(依赖注入) AOP(面向切面编程)
  • spring mvc
  • Spring 应用事件/监听器 ✔
  • Spring Environment 抽象 ✔

打对号的是针对SpringApplication 这个引导类所需要了解的。

Spring-boot 衍生技术

  • SpringApplication
  • SpringApplication Builder API
  • SpringApplication 运行监听器
  • SpringApplication 参数
  • SpringApplication 故障分析
  • SpringApplication 应用事件/监听器

SpringApplication的准备阶段也可以说是构造器阶段。下面我们来看一下源码。

/**
 * Create a new {@link SpringApplication} instance. The application context will load
 * beans from the specified primary sources (see {@link SpringApplication class-level}
 * documentation for details. The instance can be customized before calling
 * {@link #run(String...)}.
 * @param resourceLoader the resource loader to use
 * @param primarySources the primary bean sources
 * @see #run(Class, String[])
 * @see #setSources(Set)
 */
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
   this.resourceLoader = resourceLoader;
   Assert.notNull(primarySources, "PrimarySources must not be null");
   //第一部分
   this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

    
   //第二部分
   this.webApplicationType = deduceWebApplicationType();
    
    
    //第三部分
   setInitializers((Collection) getSpringFactoriesInstances(
         ApplicationContextInitializer.class));
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    
    
    //第四部分
   this.mainApplicationClass = deduceMainApplicationClass();
}

在准备阶段主要有四部分

  • 第一部分:配置我们的源,从源码的注释中我们也可以了解到这个源是可以从应用上下文中加载所有bean的指定的主要来源。通俗点讲也就是我们标注@SpringBootApplication模式注释的这个类。对于模式注解,可以看我之前的整理的文章。
  • 第二部分:推断Web应用类型。
  • 第三部分:应用上下文初始器 和 应用事件监听器,这部分可以查看Spring-boot---SpringApplication准备阶段的加载(二)
  • 第四部分:推导引用主类。

配置:Spring Bean 来源

JAVA配置Class

Spring 模式注解所标注的类,用于 Spring 注解驱动中 Java 配置类。

比如我们的项目默认的启动类,在我的项目中是SpringBootDiveLearnApplication

@SpringBootApplication
public class SpringBootDiveLearnApplication {

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

我们可以看到,这个引导类配置了一个spring的一个模式注解@SpringBootApplication

下面我们来看一下这个模式注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
      @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
      @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {}

在这个注解上又有两个特殊的注解 @SpringBootConfiguration @EnableAutoConfiguration 如果我们进一步向上查找 @SpringBootConfiguration 我们可以发现其实这个注解就是 @Component 的"派生"注解。而 @EnableAutoConfiguration 则是一个接口编程方式的装配注解。

因此呢 在引导类SpringBootDiveLearnApplication 中我们才会在主函数方法中的 SpringApplication.run 方法里 放入 SpringBootDiveLearnApplication 作为启动参数。

XML上下文配置文件

用于spring 传统配置驱动中的XML文件。

我们知道引导类以JAVA配置的Class作为参数时,是如何启动容器的,即SpringApplication.run(SpringBootDiveLearnApplication.class, args);

那么 XML方式是如何启动的呢

SpringApplication springApplication = new SpringApplication();
springApplication.setSources();
springApplication.run(args);

我们可以在.setSources方法里添加参数来配置源。

下面我们可以来看一下.setSources 的方法,来理解这个配置方法。

/**
 * Set additional sources that will be used to create an ApplicationContext. A source
 * can be: a class name, package name, or an XML resource location.
 * <p>
 * Sources set here will be used in addition to any primary sources set in the
 * constructor.
 * @param sources the application sources to set
 * @see #SpringApplication(Class...)
 * @see #getAllSources()
 */
public void setSources(Set<String> sources) {
   Assert.notNull(sources, "Sources must not be null");
   this.sources = new LinkedHashSet<>(sources);
}

我们可以看到注释中的英文解释,参数可以类名,包名以及XML源路径。

那么spring boot 是使用什么方法来分别加载这两种数据源的呢。

答案就是org.springframework.boot.BeanDefinitionLoader 中的构造方法

/**
 * Create a new {@link BeanDefinitionLoader} that will load beans into the specified
 * {@link BeanDefinitionRegistry}.
 * @param registry the bean definition registry that will contain the loaded beans
 * @param sources the bean sources
 */
BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
   Assert.notNull(registry, "Registry must not be null");
   Assert.notEmpty(sources, "Sources must not be empty");
   this.sources = sources;
   //JAVA 配置CLASS 
   this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
   //XML配置 
   this.xmlReader = new XmlBeanDefinitionReader(registry);
   if (isGroovyPresent()) {
      this.groovyReader = new GroovyBeanDefinitionReader(registry);
   }
   this.scanner = new ClassPathBeanDefinitionScanner(registry);
   this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
}

分别通过 AnnotatedBeanDefinitionReaderXmlBeanDefinitionReader 来读取并且将配置源解析加载为Spring Bean。

那么 BeanDefinitionLoader 又是在何处引用的呢。

是在引导类 SpringApplication 中的

/**
 * Factory method used to create the {@link BeanDefinitionLoader}.
 * @param registry the bean definition registry
 * @param sources the sources to load
 * @return the {@link BeanDefinitionLoader} that will be used to load beans
 */
protected BeanDefinitionLoader createBeanDefinitionLoader(
      BeanDefinitionRegistry registry, Object[] sources) {
   return new BeanDefinitionLoader(registry, sources);
}

这个方法中引用的,如果我们一步一步的往上找,那么最终的方法就是 SpringApplication.run() 方法。

由此我们也可以证实了 我们的结论,在spring-boot的配置阶段,可以有两个数据源。

推断:Web应用类型和主引导类

我们知道 spring-boot 不仅可以用作一个WEB工程,也可以作为非WEB工程来使用,那么我们如何去判断当前的工程师WEB还是非WEB的呢。我们可以从工作台来看出。如图,以下是非WEB类型的工作台打印的启动日志。

图5.PNG

我们可以看到,输出的日志中没有显示是使用Tomcat启动也没有8080端口。

而WEB工程的启动日志中则会有以下输出。

图6.PNG

从图中的输出日志中看出 即是Tomcat Server 又有8080端口。

spring-boot 是如何推断应用类型

根据当前应用 ClassPath 中是否存在相关实现类来推断 Web 应用的类型,包括:

  • Web Reactive: WebApplicationType.REACTIVE 对应的类路径

    org.springframework. web.reactive.DispatcherHandler

  • Web Servlet: WebApplicationType.SERVLET 对应的类路径 org.springframework.web.servlet.DispatcherServlet"

  • 非 Web: WebApplicationType.NONE

判断方法 是 org.springframework.boot.SpringApplication#deduceWebApplicationType

/**
* Springboot 启动时判断当前WEB应用类型的方法
*/
private WebApplicationType deduceWebApplicationType() {
    /**
    * 当类路径下存在Web Reactive 类路径并且
    *不存在WebApplicationType.SERVLET类路径是才会启用
    * WEB REACTIVE 容器 也就是 Netty。
    */
   if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
         && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)
         && !ClassUtils.isPresent(JERSEY_WEB_ENVIRONMENT_CLASS, null)) {
      return WebApplicationType.REACTIVE;
   }
   /**
   * 当类路径中都不存在时,会启用非WEB类型
   */
   for (String className : WEB_ENVIRONMENT_CLASSES) {
      if (!ClassUtils.isPresent(className, null)) {
         return WebApplicationType.NONE;
      }
   }
    /**
    *  如果都不满足,则默认启用TOMCAT 
    */
   return WebApplicationType.SERVLET;
}

以上的方法就是判断启动应用类型的方法。

spring-boot 是如何推断启动主类

我们会奇怪,spring-boot 默认启动的类不就是主类吗? 类似于下面的代码

@SpringBootApplication
public class SpringBootDiveLearnApplication {

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

我们可以清楚的看到 主类就是 SpringBootDiveLearnApplication 但是大家不要忘了,spring-boot有多种的启动方式,而且 SpringApplication.run(); 中的第一个参数是可变的,换句话说,我拿其他的类并加上 @SpringBootApplication 注解,放入SpringApplication.run(); 方法中,那么那个类就应该是主类,而不是当前类。那么到底spring-boot 是如何判断Main Class 的呢。

参考方法 : org.springframework.boot.SpringApplication#deduceMainApplicationClass

/**
* 使用异常堆栈来判断 当前主类
*/
private Class<?> deduceMainApplicationClass() {
   try {
       
      StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
      for (StackTraceElement stackTraceElement : stackTrace) {
         if ("main".equals(stackTraceElement.getMethodName())) {
            return Class.forName(stackTraceElement.getClassName());
         }
      }
   }
   catch (ClassNotFoundException ex) {
      // Swallow and continue
   }
   return null;
}

spring 是使用异常堆栈来遍历最后判断出main方法的。让我们来Debugger一下。

图7.PNG

我们可以看到 只有最后一个堆栈信息,即类名为 SpringBootDiveLearnApplication 的类中的方法名是main,从而推导出 SpringBootDiveLearnApplication 就是主类。

spring-boot的启动主类,一切都是由spring 自己推导的,所以我们不要所以然的认为主类是我们传给spring容器的。

在文章的结尾特别推荐一篇博文,也是最近才发现的,发现写的很好,我们可以根据博文里的内容,自己去debugger从而更深的理解spring Framework。

文章链接 : 给你一份Spring Boot知识清单

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容