SpringApplication.run(Main.class, args);

从这个方法开始讲吧:

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
  return run(new Class<?>[] { primarySource }, args);
}

ConfigurableApplicationContext 这个接口,熟悉spring源码的童鞋们肯定一眼就会有亲切感

至于不熟悉的童鞋们嘛...未来我可能会再开一个spring的坑

这里不详细介绍了

回到这个方法本身:

方法内容很简单,指向了另一个run方法:

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
  return new SpringApplication(primarySources).run(args);
}

先创建了一个SpringApplication的对象,

然后再次指向了一个新的run方法。

不过在我们看下一个run方法之前,按照顺序,

我们先来看一下SpringApplication的这个构造方法:

public SpringApplication(Class<?>... primarySources) {
  this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
  //将resourceLoader属性设置为同名参数
  this.resourceLoader = resourceLoader;
  //判定primarySources是否为空,若为空则 throw new IllegalArgumentException("PrimarySources must not be null");
  Assert.notNull(primarySources, "PrimarySources must not be null");
  //确定primarySources不为空的前提下,将primarySource注入到属性primarySources中
  this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
  //设置属性webApplicationType
  //关于deduceWebApplicationType()这个方法,暂且按下不表,后面会讲到
  this.webApplicationType = deduceWebApplicationType();
  //通过setter方法设置了initializers和listeners两个属性
  //至于他到底设置了个什么东西嘛,后面会讲到
  setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
  setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  //最后,设置属性mainApplicationClass
  //deduceMainApplicationClass()这个方法我们后面来讲
  this.mainApplicationClass = deduceMainApplicationClass();
}

简单总结一下就是:

  进行了一轮初始化;

  设置了一大堆不知道什么鬼用的参数

对象生成了以后,我们来看看这个run方法...

等等

本着刨根问底的精神,我们先解决前面悬而未决的问题:

关于deduceWebApplicationType();这个方法:

private WebApplicationType deduceWebApplicationType() {
  //这里有三个参数
  //REACTIVE_WEB_ENVIRONMENT_CLASS : dispatcherHandler的全类名
  //MVC_WEB_ENVIRONMENT_CLASS : dispatcherServlet的全类名
  //WEB_ENVIRONMENT_CLASSES : 是一个String[],里面有两项:
  //一个是Servlet的全类名,一个是ConfigurableWebApplicationContext的全类名
  //而isPresent方法,简单讲就是判断这个类是否存在,存在则返回true,反之返回false
  //所以说spring boot项目中maven的dependency不能随便搞,不能多加也不能少加。
  //spring boot会根据你的依赖来判断你这个项目的性质
  //后面还会有很多这种根据依赖的不同来判定的地方
  //然后说一下这个WebApplicationType:
  //是一个枚举类,里面有三项:
  //SERVLET,REACTIVE,NONE
  //只起一个标识作用,标明你这个项目到底是一个servlet项目,还是reactive项目,还是不联网(none)的项目
if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}

所以,前面构造器中:

  this.webApplicationType = deduceWebApplicationType();

就是根据你的jar包依赖,设定了这个项目的类型

然后是我们的两个setter方法:

setInitializers 和 setListeners

你可能会很奇怪,为什么都有构造器了,还要用setter方法。

答案很简单啦,因为这个setter方法显然不会是那种简单的:

  this.xx = xx;

这点东西而已啦。

虽然,倒也没复杂到哪去:

public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
this.initializers = new ArrayList<>();
this.initializers.addAll(initializers);
}

由以上代码可知,这个initializers就是一个ArrayList,把你参数中的itializers里面的所有项都放了进去。就酱;

public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
this.listeners = new ArrayList<>();
this.listeners.addAll(listeners);
}

除了变量名和参数泛型之外,好像就跟上面那个一模一样= =

两个setter方法讲完了,不过构造器中这两个setter方法的参数,是由一个叫getSpringFactoriesInstances这个方法

下一步我们来看这个方法:

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
  //获取当前线程的类加载器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates(这个是官方注释,意思是说用互不相同的名字来确保不发生重复)
  //至于这个loadFactoryNames这个方法,简单讲就是根据接口或者抽象类的class对象和类加载器,查找所有对应的工厂类的全类名
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
  //顾名思义,createSpringFactoriesInstances就是创建Spring工厂的实例
  //这个方法的具体代码我们等下来看
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
  //简单讲,这就是排了个序。
  //稍微详细点讲,根据对象的order value进行排序
  //更详细的讲,等我开spring坑再说
  //事先声明,这坑我不一定会开= =
  //这坑太大了
  //写spring是春天,讲spring是冬天
AnnotationAwareOrderComparator.sort(instances);
  //排完序了,返回回去。
return instances;
}

所以上面的方法,实际重点就是那个createSpringFactoriesInstances。

再次说明一下上面那个方法中,我们调用createSpringFactoriesInstances方法时的五个参数:

type:一个Class对象。在前面的构造器中:

setInitializers方法里注入的是ApplicationContextInitializer.class这个Class对象,

setListeners方法里注入的是ApplicationListener.class这个Class对象。

paramerterTypes:一个Class数组。

在getSpringFactoriesInstances(Class<T> type)方法中,他注入的是一个空数组。

classLoader:类加载器。方法中用的是线程的类加载器。

args:一堆参数。

在getSpringFactoriesInstances(Class<T> type)方法中,他注入的是null。

names:一个LinkedHashSet<String>

由上面的type和classloader找到这个type所有对应的工厂类的全类名

好的请记住这些,来看下面的方法:

private <T> List<T> createSpringFactoriesInstances(
      Class<T> type,
      Class<?>[] parameterTypes,
      ClassLoader classLoader,
      Object[] args,
      Set<String> names) {
  //很显然,instances就是我们最终要返回的list
  //从这个arraylist的范围来看,很显然这个list里的每一项都跟names有关
  List<T> instances = new ArrayList<>(names.size());
  for (String name : names) {
    try {
      //根据name和classloader来找类对象
      //所以前面一定要保证这里的Names不一样不然list里面就会有重复的了。
      Class<?> instanceClass = ClassUtils.forName(name, classLoader);
      //简单讲,看看type是不是instanceClass的超类或者接口。
      //如果是,往下走;如果不是,报错。
      Assert.isAssignable(type, instanceClass);
      //如果代码执行到此,说明type是instanceClass的超类。
      //parameterTypes由此可见是instanceClass的构造器的参数类型表
      Constructor<?> constructor = instanceClass
          .getDeclaredConstructor(parameterTypes);
      //简单讲,就是用刚刚得到的构造器,和args来构造一个T的实例
      //因此args和parameterTypes是一一对应的。
      //前面已经判定过,type是instanceClass的超类或者接口。
      //因此这个强制类型转换是成立的。
      T instance = (T) BeanUtils.instantiateClass(constructor, args);
      //把这个实例加到最开始声明的list里面
      instances.add(instance);
    } catch (Throwable ex) {
      //前面如果报错了,那就再报个错
      //spring向来如此,报错一个接着一个
      //最终的效果就是,不管出了什么p大点儿错,都要先给你报满满一大长串的错
      //然后每次看console...哎,头大
      throw new IllegalArgumentException(
          "Cannot instantiate " + type + " : " + name, ex);
    }
  }
  //最终把最上面那个list返回回去
  return instances;
}

综上所述,上面的方法就是创建了所有type所对应的工厂类的类对象。

此之谓createSpringFactoriesInstances

回到前面的构造方法:

setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

就是将initializers初始化为ApplicationContextInitializer.class 所对应的所有工厂类的对象组成的集合

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

就是将listeners初始化为ApplicationListener.class所对应的所有工厂类的对象组成的集合

至此,构造方法完毕。

然后我们看run方法。。。

看个鬼啊,写这么多,你们看着累不?

我反正累了。

剩下的下次再说。

拜拜~~~~~~~~~

Spring boot 源码分析(一)SpringApplication.run(上)的更多相关文章

  1. spring boot源码分析之SpringApplication

    spring boot提供了sample程序,学习spring boot之前先跑一个最简单的示例: /* * Copyright 2012-2016 the original author or au ...

  2. 精尽Spring Boot源码分析 - SpringApplication 启动类的启动过程

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  3. Spring Boot源码分析-启动过程

    Spring Boot作为目前最流行的Java开发框架,秉承"约定优于配置"原则,大大简化了Spring MVC繁琐的XML文件配置,基本实现零配置启动项目. 本文基于Spring ...

  4. 精尽Spring Boot源码分析 - 内嵌Tomcat容器的实现

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  5. 精尽Spring Boot源码分析 - 支持外部 Tomcat 容器的实现

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  6. 精尽Spring Boot源码分析 - 配置加载

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  7. 精尽Spring Boot源码分析 - 日志系统

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  8. Spring Boot源码分析-配置文件加载原理

    在Spring Boot源码分析-启动过程中我们进行了启动源码的分析,大致了解了整个Spring Boot的启动过程,具体细节这里不再赘述,感兴趣的同学可以自行阅读.今天让我们继续阅读源码,了解配置文 ...

  9. 精尽Spring Boot源码分析 - 序言

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  10. 精尽Spring Boot源码分析 - Jar 包的启动实现

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

随机推荐

  1. C#超简单自定义事件

    我知道你为啥点进来,所以不要犹豫了,立刻马上果断创建控制台项目,直接复制下面精干短小而又强大的代码运行: using System; using System.Collections.Generic; ...

  2. (18)0907_CSS选择器详解

    选择器的优先级(决定那个样式生效): important: > 内联样式 > id选择器> 类和伪类 > 元素选择器 > 通配选择器> 继承样式无优先级 最大    ...

  3. react_app 项目开发 (4)_ React UI 组件库 ant-design 的基本使用

    最流行的开源 React UI 组件库 material-ui 国外流行(安卓手机的界面效果)文档 ant-design 国内流行 (蚂蚁金服 设计,一套 PC.一套移动端的____下拉菜单.分页.. ...

  4. 关于finally代码块是否一定被执行的问题

    一般来说,只要执行了try语句,finally就会执行 但是,有以下几种情况需要特殊考虑 具体例子看链接  点击这里 第一点 try代码块没有被执行,意思就是错误在try代码块之前就发生了. 第二点 ...

  5. 关于“svn: Can't connect to host '*.*.*.*': 由于连接方在一段时间后没有正确答复或连接”的解决方法

    阿里云服务器环境(PHP+Nginx+MySQL) [原因1]svnserve.conf 没写好,当然你先备份一份先: cp svnserve.conf svnserve.conf.bak 打开此文件 ...

  6. Android学好Shape不再依赖美工

    原创 2014年03月27日 15:33:41 标签: Android Shape用法 20427 先上图 其实以上效果没有让美工提供任何图片 只要学会Shape你就能实现 想怎么样就怎么样 下面介绍 ...

  7. 总结-shell脚本

    执行脚本从 svn 检出项目 vi ace.sh #!/bin/bash svn export svn://127.0.0.1/ace/demo /ace/demo 设置脚本可执行 chmod +x ...

  8. JavaEE 藏经阁

    后台技术 工作流引擎:Activiti5 ORM框架:Mybatis/Hibernate JPA Web框架:SpringMVC/Struts 核心框架:Spring Framework4.0 任务调 ...

  9. MGR

    单主模式 参数修改 server_id=1 gtid_mode=ON enforce_gtid_consistency=ON binlog_checksum=NONE log_bin=binlog l ...

  10. [dev][ipsec] netlink是什么

    介绍: https://www.linuxjournal.com/article/7356 大纲: man手册 http://man7.org/linux/man-pages/man7/netlink ...