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. html5 Canvas绘制时钟以及绘制运动的圆

    1.绘制时钟 <!-- js代码 --> <script type="text/javascript"> window.onload=function(){ ...

  2. springmvc 学习资料

    https://github.com/brianway/springmvc-mybatis-learninghttps://www.bilibili.com/video/av18288362?from ...

  3. JavaBean,POJO,VO,DTO的区别和联系

    JavaBean 是一种JAVA语言写成的可重用组件.为写成JavaBean,类必须是具体的和公共的,并且具有无参数的构造器.JavaBean 通过提供符合一致性设计模式的公共方法将内部域暴露成员属性 ...

  4. ASCII编码查看

    实例说明 ASCII是American Standard Code Information Interchange的缩写,是基于拉丁字母的一套电脑编码系统,主要用于显示英文字符,是目前世界上最通用的单 ...

  5. MySql 主从复制 mysql-proxy实现读写分离

    1.安装和配置Docker 服务器版本阿里云CentOS7.4 docker版本18.06.0-ce docker安装步骤https://docs.docker.com/install/linux/d ...

  6. mysql 外键约束及表关联

    一.MYSQL中的约束 1.主键:primary key 唯一非空的特性并且可以优化查询速度 2.外键:foreign key 外键的作用保证2个或2个以上的数据表的数据一致性和完整性 3.唯一:un ...

  7. 线性表->链式存储->循环链表

    文字描述 循环链表是另一种形式的链式存储结构.它的特点是表中最后一个结点的指针域指向头结点,整个链表形成一个环.由此,从表中任一结点出发均可找到表中其他结点. 示意图 算法分析 插入.删除.查找等同单 ...

  8. python转换图片格式

    在图片所在的路径下,打开命令窗口 bmeps -c picturename.png picturename.eps

  9. Alibaba, I'm interested in you.

    Working for Alibaba is an aspiration for some. For other it’s the possibility of lucrative stock opt ...

  10. ytkah常用网址导航 关于网站运营等

    关于运营的网站 人人都是产品经理 产品100 爱运营 A5网站运营 姑婆那些事儿 馒头商学院 运营者 91运营网 互联网的一些事 jb51网站运营 三联网站运营 从零开始做运营 ​ 科技/互联网 cn ...