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. 《Ray Tracing in One Weekend》、《Ray Tracing from the Ground Up》读后感以及光线追踪学习推荐

    <Ray Tracing in One Weekend> 优点: 相对简单易懂 渲染效果相当好 代码简短,只看书上的代码就可以写出完整的程序,而且Github上的代码是将基类与之类写在一起 ...

  2. HDU.5385.The path(构造)

    题目链接 最短路构造题三连:这道题,HDU4903,SRM590 Fox And City. \(Description\) 给定一张\(n\)个点\(m\)条边的有向图,每条边的边权在\([1,n] ...

  3. Nestjs Graphql

    文档 工作示例 安装依赖: npm i --save @nestjs/graphql apollo-server-express graphql-tools graphql app.module.ts ...

  4. jquery easyui datagrid 如何第一次点击列标题时是降序排列

    使用 EasyUI的onBeforeLoad事件,在发回到服务器查询之前,修改排序和对应的图标样式. 1.配置回调函数 data-options='onBeforeLoad:fnOnBeforeLoa ...

  5. 解决archlinux下QT程序,以及wineQQ无法输入中文(.xinitrc)

    昨天安了i3wm,发现fcitx在很多程序中无法输入中文,nixnote2,还有ss-qt5 查了wiki,明明有在~/.xinitrc中加入 export XMODIFIERS=@im=fcitx ...

  6. 快速搭建一个直播Demo

    缘由 最近帮朋友看一个直播网站的源码,发现这份直播源码借助 阿里云 .腾讯云这些大公司提供的SDK 可以非常方便的搭建一个直播网站.下面我们来给大家讲解下如何借助 腾讯云 我们搭建一个简易的 直播示例 ...

  7. redis加固

    一.背景描述 1.漏洞描述 Redis 因配置不当存在未授权访问漏洞,可以被攻击者恶意利用. 在特定条件下,如果 Redis 以 root 身份运行,黑客可以给 root 账号写入 SSH 公钥文件, ...

  8. sudoers权限管理

    该/etc/sudoers文件的权限管理很完善,覆盖了linux中的各种命令,各种shell.编辑器等等,在此留作以后作为参考. # This file MUST be edited with the ...

  9. struct ifreq学习和实例

    一.struct ifreq结构体 这个结构定义在/usr/include/net/if.h,用来配置和获取ip地址,掩码,MTU等接口信息的. /* Interface request struct ...

  10. 数据库设计入门及ERMaster的安装和使用

    数据库的设计步骤 1.标识表  (根据需求创建表) 2.标识表的字段 3.标识表与表之间的关系 注意事项: 三大范式: 1.确保标识的字段的原子性,字段的概念分的不能再分 2.确保字段与表有依赖的关系 ...