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之后
- 页面配置导入到开发工具中
- 进行代码编写
- 运行
- 引入依赖parent
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath />
</parent>
- 设置资源属性
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
- 引入依赖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文件。
构建聚合工程
- 聚合工程里可以分为顶级项目(顶级工程、父工程)与子工程,这两者的关系其实就是父子继承的关系子工程在maven里称之为模块(module),模块之间是平级,是可以相互依赖的。
- 子模块可以使用顶级工程里所有的资源(依赖),子模块之间如果要使用资源,必须构建依赖(构建关系)
- 一个顶级工程是可以由多个不同的子工程共同组合而成。
springboot2 jdbc数据源配置
HikariCP:一个高性能的jdbc连接池。
Mybatis:一个优秀的持久层框架,避免所有的JDBC代码和手动设置参数以及获取结果集。可以使用简单的XML或注解来配置和映射原生信息。
- 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>
- 在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&serverTimezone=Asia/Shanghai&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();
通过反射的方式创建了
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();
}
}
}