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. 2017-2018 ACM-ICPC, NEERC, Moscow Subregional Contest

    A. Advertising Strategy 最优策略一定是第一天用$y$元,最后一天再用$x-y$元补满. 枚举所有可能的$y$,然后模拟即可,天数为$O(\log n)$级别. 时间复杂度$O( ...

  2. 4. Spring 如何通过 XML 文件配置Bean,以及如何获取Bean

    在 Spring 容器内拼凑 bean 叫做装配.装配 bean 的时候,你是在告诉容器,需要哪些 bean ,以及容器如何使用依赖注入将它们配合在一起. 理论上,bean 装配的信息可以从任何资源获 ...

  3. JQuery 目录树jsTree插件用法

    PHP循环构造目录树结构 <ul> <php> function digui($fid,$level){ $class=M("wangpan_class") ...

  4. 简单的3d变换

    <!DOCTYPE html><html> <head>  <meta charset="UTF-8">  <title> ...

  5. 运用JS判断代码可以参考学习

    JAVAScript代码加CSS和HTML <%-- Created by IntelliJ IDEA. User: zengxiangcai Date: 2018/6/27 Time: 11: ...

  6. 客户端无法加入域,报错:“无法与域‘xxx.com’的Active Directory域控制器(AD DC)链接” 请确保键入的域名正确

    1.客户端能不能解析到域名? nslookup 一下域名看看解析到的IP的地址 2.客户端的DNS要指向DC 3.客户端的相关服务,workstation,TCP/IP NetBios Helper, ...

  7. git分支与主干合并操作

    git分支与主干合并操作1.主干合并分支在主干上合并分支 branch (master)git merge branch --squash 提交合并后的代码 (master)git commit -m ...

  8. [ipsec][crypto] 什么是AEAD加密算法中的AAD 及aad length

    AAD 全称:Additianal Authenticated Data 翻译成中文就是附加的验证数据. 在理解AAD之前,需要理解什么是AEAD: AEAD,简单的来说就是一份数据在完成加密的时候同 ...

  9. mysql.user表详解

    GRANT语法:     GRANT 权限 ON 数据库.* TO 用户名@'登录主机' IDENTIFIED BY '密码'  权限:  ALL,ALTER,CREATE,DROP,SELECT,U ...

  10. python之元组

    一.元组定义 元组(tuple)使用小括号()来定义,它是不可变数据类型,它的元素不可变,但元素的元素是可变的,比如tuple中一个元素为list列表时,list中的元素是可变的 # 在定义tuple ...