Spring boot 源码分析SpringApplication.run(上)

Posted xpecya

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring boot 源码分析SpringApplication.run(上)相关的知识,希望对你有一定的参考价值。

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(上)的主要内容,如果未能解决你的问题,请参考以下文章

spring boot 2.0 源码分析

Spring Boot 启动流程源码分析

spring boot 源码分析-------ApplicationContext

spring boot 2.0 源码分析

SpringBoot 源码解析 ----- Spring Boot的核心能力 - 内置Servlet容器源码分析(Tomcat)

Spring boot 源码分析(前言)