java从0到1系统学习系列-基础准备

348 阅读14分钟

springboot项目

什么是springboot?

  • 是一款全新的框架,目的是为简化spring应用的初始搭建和开发过程,使用特定的方式来进行配置,从而使开发人员不需要定义样板化配置,达到"开箱即用"。
  • 核心思想是:约定大于配置。  springboot所有开发细节都是根据此思想进行实现的。

什么是约定大于配置?

  • 是一种软件设计范式,为减少开发人员需要做决定的数量,减少开发成本,提高沟通效率,快速上手。
  • 举个栗子,springboot JPA就是约定大于配置最佳实践之一,不需要关注表结构,我们约定类名即是表名,属性名即是表的字段。只有需要特殊要求的属性,才需要单独配置。

什么是starter?

  • Starters基于约定大于配置的理念来设计,Springboot Starter中有两个核心组件:自动配置代码和提供自动配置模块及其它有用依赖。也就是说,当我们项目引入某个Starter时,项目就拥有此软件的默认使用能力,除非是我们需要的特定配置。一般情况下我们仅需少量配置或者不配置就可使用组件功能。
  • 在传统Maven项目中通常将一些层、组件拆分为模块来管理,以达到相互依赖复用的作用,在Springboot中我们则可以自定义Starter来达到该目的。

为什么需要springboot
springboot本身并不提供spring框架的核心特性以及扩展功能,只是用于快速、敏捷地开发。同时他也集成了大量常用的第三方库配置(如Redis、Mongo等),大部分springboot应用中这些第三方库几乎可以零配置进行开箱即用,开发者只需要更加关注于业务逻辑。

  • springboot使开发变得简单。快速集成了各种解决方案提升开发效率。
  • springboot使配置变得简单。提供了丰富的starters,集成主流开源产品只需要简单配置即可。
  • springboot使部署变得简单。本身内嵌容器,仅仅需要一个命令即可启动项目。
  • springboot使监控变得简单。自带监控工具,使用Actuator轻松监控各项状态。

spring、springboot、springcloud之间有什么关系?

  • Spring最初核心的两大功能IOC和AOP成就了Spring,在次基础上不断发展,才有了Spring事务、Spring MVC等一系列伟大的产品,最终成就了Spring帝国。
  • SpringBoot不是为了替代Spring,而是使用约定大于配置的理念,重新重构了Spring的使用,让Spring后续的发展更有活力。
  • SpringBoot将市面上各家公司开发的比较成熟、经得起考验的框架组合起来,通过Springboot风格进行封装再解决复杂的配置和实现原理,最终给开发者提供一套简单易懂、易维护、易部署的分布式系统开发工具包。
  • SpringCloud是一系列框架的有序集合,它利用Springboot的开发便利性简化分布式系统基础设施的开发。

使用springboot的前后对比

  • 使用springboot之前
    • 配置web.xml,加载spring和spring mvc
    • 配置数据库连接,配置spring事务
    • 配置加载配置文件的读取,开启注解
    • 配置日志文件
    • 配置完成之后部署tomcat调试
  • 使用springboot之后
    • 页面配置导入到开发工具中
    • 进行代码编写
    • 运行
  1. 引入依赖parent
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.5.RELEASE</version>
    <relativePath />
</parent>
  1. 设置资源属性
<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
</properties>
  1. 引入依赖dependency
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-logging</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- spring默认使用yml中的配置,但有时候要用传统的xml或properties配置,就需要使用spring-boot-configuration-processor了 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

自动装配概述

实际上SpringBoot的自动装配原理,其实就是在项目启动的时候去加载META-INF下的spring.factories文件,好像也没有那么高大上。当然在启动的过程中还会有其他的配置项的加载,这里咱么直说了自动装配的加载过程。希望对大家可以有所启发。

首先看下springboot的启动的run方法:

/**
 * Static helper that can be used to run a {@link SpringApplication} from the
 * specified source using default settings.
 */
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
      String[] args) {
   return new SpringApplication(primarySources).run(args);
}

其中default settings就是我们所说的自动装配。具体生效的地方就是我们在应用启动类中添加了@SpringBootApplication注解。

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
      @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
      @Filter(type = FilterType.CUSTOM,
            classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

关键点就是@EnableAutoConfiguration注解。

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration

@Import就是导入类(不仅包括配置类,也可以是普通类,或者是ImportSelector的实现等)。 进入AutoConfigurationImportSelector类。 逐步下钻到getCandidateConfigurations,可以看到在META-INF/spring.factories中加载配置类。故自动装载的重点是spring.factories文件。

构建聚合工程

  1. 聚合工程里可以分为顶级项目(顶级工程、父工程)与子工程,这两者的关系其实就是父子继承的关系子工程在maven里称之为模块(module),模块之间是平级,是可以相互依赖的。
  2. 子模块可以使用顶级工程里所有的资源(依赖),子模块之间如果要使用资源,必须构建依赖(构建关系)
  3. 一个顶级工程是可以由多个不同的子工程共同组合而成。

springboot2 jdbc数据源配置

HikariCP:一个高性能的jdbc连接池。
Mybatis:一个优秀的持久层框架,避免所有的JDBC代码和手动设置参数以及获取结果集。可以使用简单的XML或注解来配置和映射原生信息。

  1. pom中引入数据源驱动与mybatis依赖
<!-- mysql驱动 -->
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<version>5.1.41</version>
</dependency>
<!-- mybatis -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.0</version>
</dependency>
  1. 在yml中配置数据源和mybatis
############################################################
#
# 配置数据源信息
#
############################################################
spring:
  datasource:                                           # 数据源的相关配置
      type: com.zaxxer.hikari.HikariDataSource          # 数据源类型:HikariCP
      driver-class-name: com.mysql.jdbc.Driver          # mysql驱动
      url: jdbc:mysql://localhost:3306/foodie-shop-dev?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true
      username: root
      password: root
    hikari:
      connection-timeout: 30000       # 等待连接池分配连接的最大时长(毫秒),超过这个时长还没可用的连接则发生SQLException, 默认:30秒
      minimum-idle: 20                # 最小连接数
      maximum-pool-size: 20           # 最大连接数
      auto-commit: true               # 自动提交
      idle-timeout: 600000            # 连接超时的最大时长(毫秒),超时则被释放(retired),默认:10分钟
      pool-name: DateSourceHikariCP     # 连接池名字
      max-lifetime: 1800000           # 连接的生命时长(毫秒),超时而且没被使用则被释放(retired),默认:30分钟 1800000ms
      connection-test-query: SELECT 1
          
############################################################
#
# mybatis 配置
#
############################################################
mybatis:
  type-aliases-package: com.imooc.pojo          # 所有POJO类所在包路径
  mapper-locations: classpath:mapper/*.xml      # mapper映射文件

MyBatis逆向生成工具-tkmybatis

graph TD
数据库 --> Mybatis-generaror
Mybatis-generaror --> pojo
Mybatis-generaror --> *mapper.xml
Mybatis-generaror --> *mapper.java
Mybatis-generaror -.- 集成MyMapper插件

如何使用呢?

  • 独立创建项目:gitee:gitee.com/xxx89/geekt…
  • 配置resources中generatorConfig.xml:
    • jdbc连接信息
    • 数据表
    • 通用mapper所在目录
    • 生成pojo信息
    • 生成mapper.xml所在目录
    • 生成mapper.java信息
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
    <context id="MysqlContext" targetRuntime="MyBatis3Simple" defaultModelType="flat">
        <property name="beginningDelimiter" value="`"/>
        <property name="endingDelimiter" value="`"/>

        <!-- 通用mapper所在目录 -->
        <plugin type="tk.mybatis.mapper.generator.MapperPlugin">
            <property name="mappers" value="com.xxx.mapper.base.MyMapper"/>
        </plugin>

        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/foodie-dev?useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;nullCatalogMeansCurrent=true"
                        userId="root"
                        password="12345678">
        </jdbcConnection>

        <!-- 对应生成的pojo所在包 -->
        <javaModelGenerator targetPackage="com.xxx.pojo" targetProject="mybatis-generator-for-imooc/src/main/java"/>

      <!-- 对应生成的mapper所在目录 -->
        <sqlMapGenerator targetPackage="mapper" targetProject="mybatis-generator-for-imooc/src/main/resources"/>

      <!-- 配置mapper对应的java映射 -->
        <javaClientGenerator targetPackage="com.xxx.mapper" targetProject="mybatis-generator-for-imooc/src/main/java" type="XMLMAPPER"/>

        <!-- 数据库表 -->
      <table tableName="carousel"></table>
        <table tableName="category"></table>
        <table tableName="users"></table>
        <table tableName="user_address"></table>
        <table tableName="items"></table>
        <table tableName="items_img"></table>
        <table tableName="items_spec"></table>
        <table tableName="items_param"></table>
        <table tableName="items_comments"></table>
        <table tableName="orders"></table>
        <table tableName="order_items"></table>
        <table tableName="order_status"></table>

    </context>
</generatorConfiguration>
  • 运行反向工程,将生成的pojo、mapper.java、mapper.xml、通用mapper拷贝到业务工程中。
  • 配置业务工程。
    • 在pom中引入通用mapper工具。
    <!-- 通用mapper逆向工具 -->
    <dependency>
        <groupId>tk.mybatis</groupId>
        <artifactId>mapper-spring-boot-starter</artifactId>
        <version>2.1.5</version>
    </dependency>
    
    • 在yml中引入通用mapper配置
    ############################################################
    #
    # mybatis mapper 配置
    #
    ############################################################
    # 通用 Mapper 配置
    mapper:
      mappers: com.imooc.my.mapper.MyMapper
      not-empty: false
      identity: MYSQL
    

通用Mapper是个啥
提供了针对单表的CRUD方法,大大提高了开发效率。

TODO
建议使用Mybatis-plus,具体使用对比:www.jianshu.com/p/d4d2362d7… 后续对Mybatis-plus进行补充。

事务是个啥

示例:

@Transactional(propagation = Propagation.SUPPORTS)
传播类型解释栗子
SUPPORTS【一般在查询中用】如果当前有事务,则使用事务;如果当前没有事务,则不使用事务。领导没饭吃,我也没饭吃;领导有饭吃,我也有饭吃。
REQUIRED(默认)【一般在增删改中用】使用当前的事务,如果当前没有事务,则自己新建一个事务,子方法是必须运行在一个事务中的;如果当前存在事务,则加入这个事务,成为一个整体。领导没饭吃,我有钱,我会自己买了自己吃;领导有的吃,会分给你一起吃。
MANDATORY该传播属性强制必须存在一个事务,如果不存在,则抛出异常领导必须管饭,不管饭没饭吃,我就不乐意了,就不干了(抛出异常)
REQUIRES_NEW如果当前有事务,则挂起该事务,并且自己创建一个新的事务给自己使用;如果当前没有事务,则同 REQUIRED领导有饭吃,我偏不要,我自己买了自己吃
NOT_SUPPORTED如果当前有事务,则把事务挂起,自己不适用事务去运行数据库操作领导有饭吃,分一点给你,我太忙了,放一边,我不吃
NEVER如果当前有事务存在,则抛出异常领导有饭给你吃,我不想吃,我热爱工作,我抛出异常
NESTED如果当前有事务,则开启子事务(嵌套事务),嵌套事务是独立提交或者回滚;如果当前没有事务,则同 REQUIRED。但是如果主事务提交,则会携带子事务一起提交。如果主事务回滚,则子事务会一起回滚。相反,子事务异常,则父事务可以回滚或不回滚。领导决策不对,老板怪罪,领导带着小弟一同受罪。小弟出了差错,领导可以推卸责任。

为啥没有使用@EnableTransactionManagement,也能开始事务?
因为@SpringbootApplication注解中的自动注入spring.factories中的TransactionAutoConfiguration。所以默认生效了。

数据库建模工具-PDMAN

文档地址:www.pdman.cn/#/

数据库建模过程精细提炼,化繁为简,省去不必要的操作,只留下最需要的,直截了当的展现给用户。

最新版本功能削减了很多,比如版本控制、连接数据库等。推荐使用老版本:2.1.6

亮点功能

  • 配置建表的默认字段。
  • 创建表。
  • 数据库逆向解析。
  • 模型版本管理。
  • 数据库连接。

为啥不用数据库外键(大型项目中禁止使用)

  • 性能影响。
  • 热更新。
  • 降低耦合度。
  • 数据库分库分表。

加餐:探索JVM

jvm运行流程

graph LR
java源程序 --javac 编译--> 字节码文件
字节码文件  --> JVM虚拟机
subgraph 解释本地指令
JVM虚拟机  -.- Windows
JVM虚拟机  -.- Linux
JVM虚拟机  -.- Other
end

jvm基本结构

graph LR
Class文件 --> Classloader
Classloader  --> 运行时数据区
运行时数据区  --> id1[执行引擎,本地库接口]
id1[执行引擎,本地库接口]  --> 本地方法库

类加载器

  • Bootstrap Classloader -> rt.jar
  • Extension Classloader -> %JAVA_HOME%/lib/ext/*.jar
  • APP Classloader -> Classpath
  • 自定义Classloader -> 完成自定义加载路径

自定义一个ClassLoader

  • 重写ClassLoader中的findClass,指定特定逻辑。
  • ClassLoader中的loadClass逻辑:双亲委派机制。
protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                c = findClass(name);

                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}
package com.xxx;


import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

public class XxxClassloader extends ClassLoader {

    private String name;
    private String path;

    public XxxClassloader(String name, String path) {
        this.name = name;
        this.path = path;
    }

    public XxxClassloader(ClassLoader loader, String name, String path) {
        super(loader);
        this.name = name;
        this.path = path;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] bs = readClassFileToByteArray(name);
        return this.defineClass(name, bs, 0, bs.length);
    }

    private byte[] readClassFileToByteArray(String name) {
        byte[] data = null;
        name = name.replaceAll("\.", "/");
        String filepath = this.path + name + ".class";
        File file = new File(filepath);
        try (InputStream is = new FileInputStream(file);ByteArrayOutputStream os = new ByteArrayOutputStream()){
            int tmp = 0;
            while((tmp = is.read()) != -1) {
                os.write(tmp);
            }
            data = os.toByteArray();
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return data;
    }
}

测试类:

  • 不指定parent类加载器,默认就是APPClassLoader。见下面的ClassLoader cl
  • 指定parent为null,则父加载器就是BootstrapClassLoader。见下面的ClassLoader cl1
package com.xxx;

public class ClassLoaderDemo {

    public ClassLoaderDemo() {
        System.out.println("A ClassLoaderDemo. " + this.getClass().getClassLoader());
    }

    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        ClassLoader cl = new XxxClassloader("xxx", "/Users/wangxin/1.learning/tmp/");
        Class<?> c = cl.loadClass("com.xxx.ClassLoaderDemo");
        c.newInstance();

        ClassLoader cl1 = new XxxClassloader(null, "xxx1", "/Users/wangxin/1.learning/tmp/");
        Class<?> c1 = cl1.loadClass("com.xxx.ClassLoaderDemo");
        c1.newInstance();
    }
}

加餐:Spring事件

java事件/监听器编程模型

  • 设计模式 - 观察者模式扩展
    • 可观者对象(消息发送者) - Observable
    • 观察者 - Observer
  • 标准化接口
    • 事件对象 - EventObject
    • 事件监听器 - EventListener 自定义实现一个事件监听demo
package com.xxx.observer;

import java.util.EventListener;
import java.util.EventObject;
import java.util.Observable;
import java.util.Observer;

/**
 * {@link Observer} 示例
 *
 * @see Observer
 * @since
 */
public class ObserverDemo {

    public static void main(String[] args) {
        // 事件发送者
        EventObservable observable = new EventObservable();

        // 添加观察者(监听者)
        observable.addObserver(new EventObserver());
        // 发布消息(事件)
        observable.notifyObservers("hello world!");

    }
    static class EventObservable extends Observable {

        @Override
        public synchronized void setChanged() {
            super.setChanged();
        }

        @Override
        public void notifyObservers(Object arg) {
            super.setChanged();
            super.notifyObservers(new EventObject(arg));
            super.clearChanged();
        }
    }

    // 事件监听者
    static class EventObserver implements Observer, EventListener {

        @Override
        public void update(Observable o, Object message) {
            EventObject eventObject = (EventObject) message;
            System.out.println("observer : " + eventObject.toString());
        }
    }
}

加餐:springboot启动流程

启动之后,进入run()方法。

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

先看SpringApplication如何构造的

到这里会执行new SpringApplication(primarySources)创建spring应用对象。

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
   this.resourceLoader = resourceLoader;
   Assert.notNull(primarySources, "PrimarySources must not be null");
   this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
   // 推断是否是web类型
   this.webApplicationType = WebApplicationType.deduceFromClasspath();
   // 初始化应用context
   setInitializers((Collection) getSpringFactoriesInstances(
         ApplicationContextInitializer.class));
   // 设置初始化监听
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
   // 推演主程序类
   this.mainApplicationClass = deduceMainApplicationClass();
}

一、推算是否为web类型应用

static WebApplicationType deduceFromClasspath() {
   if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
         && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
         && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
      return WebApplicationType.REACTIVE;
   }
   for (String className : SERVLET_INDICATOR_CLASSES) {
      if (!ClassUtils.isPresent(className, null)) {
         return WebApplicationType.NONE;
      }
   }
   return WebApplicationType.SERVLET;
}

二、初始化应用上下文

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
      Class<?>[] parameterTypes, Object... args) {
   // 获取ClassLoader,用户可以指定,默认使用ClassUtils.getDefaultClassLoader()即:{Thread.currentThread().getContextClassLoader();},即为
   ClassLoader classLoader = getClassLoader();
   // Use names and ensure unique to protect against duplicates
   // 加载spring.factories文件中的type的类信息,type是spring.factories中的key,这边是org.springframework.context.ApplicationContextInitializer
   Set<String> names = new LinkedHashSet<>(
         SpringFactoriesLoader.loadFactoryNames(type, classLoader));
   // 实例化加载到的类
   List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
         classLoader, args, names);
   AnnotationAwareOrderComparator.sort(instances);
   return instances;
}

重点看下是如何加载类的。spring.factories资源文件涉及到spring-boot spring-boot-autoconfigure spring-bean中的文件。

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
   // 先查缓存
   MultiValueMap<String, String> result = cache.get(classLoader);
   if (result != null) {
      return result;
   }

   try {
      // 资源路径下加载spring.factories资源文件。
      Enumeration<URL> urls = (classLoader != null ?
            classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
            ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
      result = new LinkedMultiValueMap<>();
      while (urls.hasMoreElements()) {
         URL url = urls.nextElement();
         UrlResource resource = new UrlResource(url);
         Properties properties = PropertiesLoaderUtils.loadProperties(resource);
         for (Map.Entry<?, ?> entry : properties.entrySet()) {
            String factoryClassName = ((String) entry.getKey()).trim();
            for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
               // result是同一个key下面可以放多个value的map。
               result.add(factoryClassName, factoryName.trim());
            }
         }
      }
      cache.put(classLoader, result);
      return result;
   }
   catch (IOException ex) {
      throw new IllegalArgumentException("Unable to load factories from location [" +
            FACTORIES_RESOURCE_LOCATION + "]", ex);
   }
}

三、初始化监听器类 同上面一样,唯一的区别是加载ApplicationListener.class

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

四、推演主程序类 获取堆栈信息,找到main方法所在的类。返回该类信息。

private Class<?> deduceMainApplicationClass() {
   try {
      StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
      for (StackTraceElement stackTraceElement : stackTrace) {
         if ("main".equals(stackTraceElement.getMethodName())) {
            return Class.forName(stackTraceElement.getClassName());
         }
      }
   }
   catch (ClassNotFoundException ex) {
      // Swallow and continue
   }
   return null;
}

再看run方法

主方法:

public ConfigurableApplicationContext run(String... args) {
   StopWatch stopWatch = new StopWatch();
   // 启动计时器
   stopWatch.start();
   ConfigurableApplicationContext context = null;
   Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
   configureHeadlessProperty();
   // 获取所有的监听器
   SpringApplicationRunListeners listeners = getRunListeners(args);
   // 启动监听器
   listeners.starting();
   try {
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(
            args);
      // 准备环境
      ConfigurableEnvironment environment = prepareEnvironment(listeners,
            applicationArguments);
      // 配置忽略的bean
      configureIgnoreBeanInfo(environment);
      // 打印banner
      Banner printedBanner = printBanner(environment);
      // 创建容器
      context = createApplicationContext();
      // 异常处理相关
      exceptionReporters = getSpringFactoriesInstances(
            SpringBootExceptionReporter.class,
            new Class[] { ConfigurableApplicationContext.class }, context);
      // 准备应用上下文
      prepareContext(context, environment, listeners, applicationArguments,
            printedBanner);
      // 刷新容器
      refreshContext(context);
      afterRefresh(context, applicationArguments);
      stopWatch.stop();
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass)
               .logStarted(getApplicationLog(), stopWatch);
      }
      // 发布监听应用上下文启动完成
      listeners.started(context);
      // 执行runner
      callRunners(context, applicationArguments);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, listeners);
      throw new IllegalStateException(ex);
   }

   try {
      listeners.running(context);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, null);
      throw new IllegalStateException(ex);
   }
   return context;
}

下面针对关键步骤进行一一解释

一、获取所有的监听器

private SpringApplicationRunListeners getRunListeners(String[] args) {
   Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
   return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
         SpringApplicationRunListener.class, types, this, args));
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
      Class<?>[] parameterTypes, Object... args) {
   ClassLoader classLoader = getClassLoader();
   // Use names and ensure unique to protect against duplicates
   Set<String> names = new LinkedHashSet<>(
         SpringFactoriesLoader.loadFactoryNames(type, classLoader));
   List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
         classLoader, args, names);
   AnnotationAwareOrderComparator.sort(instances);
   return instances;
}

这一段逻辑我们比较熟悉了,主要作用就是在META-INFO/spring.factories中加载SpringApplicationRunListener的类。

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=
org.springframework.boot.context.event.EventPublishingRunListener

下一步就是启动listeners.starting()

public void starting() {
   this.initialMulticaster.multicastEvent(
         new ApplicationStartingEvent(this.application, this.args));
}

启动的时候实际上就是创建了一个ApplicationStartingEvent对象。 initialMulticaster是一个SimpleApplicationEventMulticaster。

this.initialMulticaster = new SimpleApplicationEventMulticaster();
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
   // 根据ApplicationStartingEvent事件类型找到对应的监听器
   ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
   for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
      // 为每一个监听事件创建一个线程池
      Executor executor = getTaskExecutor();
      if (executor != null) {
         executor.execute(() -> invokeListener(listener, event));
      }
      else {
         invokeListener(listener, event);
      }
   }
}

二、准备环境

private ConfigurableEnvironment prepareEnvironment(
			SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		// Create and configure the environment
    // 这里我们加了web的依赖所以是一个servlet容器
		ConfigurableEnvironment environment = getOrCreateEnvironment();
    // 配置环境
		configureEnvironment(environment, applicationArguments.getSourceArgs());
    //环境准备完成
		listeners.environmentPrepared(environment);
	// 	
    bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
			environment = new EnvironmentConverter(getClassLoader())
					.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

由于我们是添加了web的依赖,所以getOrCreateEnvironment()返回的是一个StandardServletEnvironment标准的servlet环境。 看着就是初始化配置信息。根据profile、环境变量等初始化配置信息。

三、配置忽略的bean

configureIgnoreBeanInfo(environment);

四、打印banner

banner内容在SpringBootBanner中。

五、创建容器

context = createApplicationContext();

image.png 通过反射的方式创建了ConfigurableApplicationContext

return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);

六、异常错误处理上报初始化 其实还是去META-INFO/spring.factories配置文件中加载SpringBootExceptionReporter

七、准备应用上下文

根据之前创建的上下文、准备的环境、监听等创建应用上下文。

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, 
    SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
        //设置环境参数
        context.setEnvironment(environment);
        //设置后处理应用上下文
        this.postProcessApplicationContext(context);
        //把从spring.properties中加载的org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,进行初始化操作
        this.applyInitializers(context);
        //EventPublishingRunLIstener发布应用上下文事件
        listeners.contextPrepared(context);
        //打印启动日志
        if (this.logStartupInfo) {
            this.logStartupInfo(context.getParent() == null);
            this.logStartupProfileInfo(context);
        }
        //注册一个字是springApplicationArguments单例的bean,
  context.getBeanFactory().registerSingleton("springApplicationArguments", applicationArguments);

   if (printedBanner != null) {
        //注册一个字是springBootBanner单例的bean,
            context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
        }
        //Load the sources 获取所有资源
        Set<Object> sources = this.getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        //创建BeanDefinitionLoader加载器加载注册所有的资源
        this.load(context, sources.toArray(new Object[0]));
        //同之前,发布应用上下文加载事件
        listeners.contextLoaded(context);
    }

八、刷新应用上下文

public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      // Prepare this context for refreshing.
      prepareRefresh();

      // Tell the subclass to refresh the internal bean factory.
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // Prepare the bean factory for use in this context.
      prepareBeanFactory(beanFactory);

      try {
         // Allows post-processing of the bean factory in context subclasses.
         postProcessBeanFactory(beanFactory);

         // Invoke factory processors registered as beans in the context.
         invokeBeanFactoryPostProcessors(beanFactory);

         // Register bean processors that intercept bean creation.
         registerBeanPostProcessors(beanFactory);

         // Initialize message source for this context.
         initMessageSource();

         // Initialize event multicaster for this context.
         initApplicationEventMulticaster();

         // Initialize other special beans in specific context subclasses.
         onRefresh();

         // Check for listener beans and register them.
         registerListeners();

         // Instantiate all remaining (non-lazy-init) singletons.
         finishBeanFactoryInitialization(beanFactory);

         // Last step: publish corresponding event.
         finishRefresh();
      }

      catch (BeansException ex) {
         if (logger.isWarnEnabled()) {
            logger.warn("Exception encountered during context initialization - " +
                  "cancelling refresh attempt: " + ex);
         }

         // Destroy already created singletons to avoid dangling resources.
         destroyBeans();

         // Reset 'active' flag.
         cancelRefresh(ex);

         // Propagate exception to caller.
         throw ex;
      }

      finally {
         // Reset common introspection caches in Spring's core, since we
         // might not ever need metadata for singleton beans anymore...
         resetCommonCaches();
      }
   }
}