数据访问
SQL
1.0 数据源的自动配置-HikariDataSource
导入 JDBC 场景
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
没有导入数据库驱动, 因为官方不知道我们接下来要操作的数据库
数据库版本与驱动版本对应
默认版本:<mysql.version>8.0.22</mysql.version>
====================================
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<!-- <version>5.1.49</version>-->
</dependency>
想要修改版本
1、直接依赖引入具体版本 (maven的就近依赖原则)
2、重新声明版本 (maven的属性的就近优先原则)
<properties>
<java.version>1.8</java.version>
<mysql.version>5.1.49</mysql.version>
</properties>
分析自动配置
- 自动配置的类
-
DataSourceAutoConfiguration: 数据源的自动配置
- 修改数据源相关的配置:spring.datasource
- 数据库连接池的配置,是自己容器中没有DataSource才自动配置的
- 底层配置好的连接池是:HikariDataSource
@Configuration(proxyBeanMethods = false) @Conditional(PooledDataSourceCondition.class) @ConditionalOnMissingBean({ DataSource.class, XADataSource.class }) @Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class, DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class, DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class }) protected static class PooledDataSourceConfiguration{ }
-
DataSourceTransactionManagerAutoConfiguration: 事务管理器的自动配置
-
JdbcTemplateAutoConfiguration: JdbcTemplate的自动配置,可以来对数据库进行crud
- 可以修改这个配置项@ConfigurationProperties(prefix = “spring.jdbc”) 来修改JdbcTemplate
- @Bean@Primary JdbcTemplate; 容器中有这个组件
- JndiDataSourceAutoConfiguration: jndi的自动配置
- XADataSourceAutoConfiguration: 分布式事务相关的
修改配置项
spring:
datasource:
url: jdbc:mysql://localhost:3306/db_account
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
测试
@Slf4j
@SpringBootTest
class Boot05WebAdminApplicationTests {
@Autowired
JdbcTemplate jdbcTemplate;
@Test
void contextLoads() {
// jdbcTemplate.queryForObject("select * from account_tbl")
// jdbcTemplate.queryForList("select * from account_tbl",)
Long aLong = jdbcTemplate.queryForObject("select count(*) from account_tbl", Long.class);
log.info("记录总数:{}",aLong);
}
}
2.0 使用 Druid 数据源
1.0 druid 官方 github 地址
https://github.com/alibaba/druid
整合第三方技术的两种方式
- 自定义
- 找starter
2.0 自定义方式
- 创建数据源
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.17</version>
</dependency>
======================== 官网 ========================
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="maxActive" value="20" />
<property name="initialSize" value="1" />
<property name="maxWait" value="60000" />
<property name="minIdle" value="1" />
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="poolPreparedStatements" value="true" />
<property name="maxOpenPreparedStatements" value="20" />
-
statViewServlet (具有强大监控功能, 具体见官方文档中需要配置哪些组件)
StatViewServlet的用途包括:
- 提供监控信息展示的html页面
- 提供监控信息的JSON API
<servlet>
<servlet-name>DruidStatView</servlet-name>
<servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DruidStatView</servlet-name>
<url-pattern>/druid/*</url-pattern>
</servlet-mapping>
-
StatFilter
用于统计监控信息;如SQL监控、URI监控
需要给数据源中配置如下属性;可以允许多个filter,多个用,分割;如:
<property name="filters" value="stat,slf4j" />
系统中所有filter:
别名 | Filter 类名 |
---|---|
default | com.alibaba.druid.filter.stat.StatFilter |
stat | com.alibaba.druid.filter.stat.StatFilter |
mergeStat | com.alibaba.druid.filter.stat.MergeStatFilter |
encoding | com.alibaba.druid.filter.encoding.EncodingConvertFilter |
log4j | com.alibaba.druid.filter.logging.Log4jFilter |
log4j2 | com.alibaba.druid.filter.logging.Log4j2Filter |
slf4j | com.alibaba.druid.filter.logging.Slf4jLogFilter |
commonlogging | com.alibaba.druid.filter.logging.CommonsLogFilter |
慢sql记录配置
<bean id="stat-filter" class="com.alibaba.druid.filter.stat.StatFilter">
<property name="slowSqlMillis" value="10000" />
<property name="logSlowSql" value="true" />
</bean>
使用 slowSqlMillis 定义慢SQL的时长
用配置文件的方式来给容器中添加 bean
@Configuration
public class MyDataSourceConfig {
// 默认的自动配置是判断容器中没有才会配 @ConditionalOnMissingBean(DataSource.class)
// @ConfigurationProperties("spring.datasource")
// @Bean
public DataSource dataSource() throws SQLException {
DruidDataSource druidDataSource = new DruidDataSource();
// druidDataSource.setUrl();
// druidDataSource.setUsername();
// druidDataSource.setPassword();
//加入监控功能
// druidDataSource.setFilters("stat,wall");
// druidDataSource.setMaxActive(10);
return druidDataSource;
}
// 配置 druid的监控页功能
// @Bean
public ServletRegistrationBean statViewServlet(){
StatViewServlet statViewServlet = new StatViewServlet();
ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(statViewServlet, "/druid/*");
registrationBean.addInitParameter("loginUsername","admin");
registrationBean.addInitParameter("loginPassword","123456");
return registrationBean;
}
// WebStatFilter 用于采集 web-jdbc 关联监控的数据。
// @Bean
public FilterRegistrationBean webStatFilter(){
WebStatFilter webStatFilter = new WebStatFilter();
FilterRegistrationBean<WebStatFilter> filterRegistrationBean = new FilterRegistrationBean<>(webStatFilter);
filterRegistrationBean.setUrlPatterns(Arrays.asList("/*"));
filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return filterRegistrationBean;
}
}
3.0 使用官 starter方式
1. 引入 druid-starter
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
2. 分析自动配置
- 扩展配置项 spring.datasource.druid
- DruidSpringAopConfiguration.class, 监控 SpringBean 的;配置项:spring.datasource.druid.aop-patterns
- DruidStatViewServletConfiguration.class, 监控页的配置:spring.datasource.druid.stat-view-servlet;默认开启
- DruidWebStatFilterConfiguration.class, web 监控配置;spring.datasource.druid.web-stat-filter;默认开启
- DruidFilterConfiguration.class}) 所有 Druid 自己 filter 的配置
private static final String FILTER_STAT_PREFIX = "spring.datasource.druid.filter.stat";
private static final String FILTER_CONFIG_PREFIX = "spring.datasource.druid.filter.config";
private static final String FILTER_ENCODING_PREFIX = "spring.datasource.druid.filter.encoding";
private static final String FILTER_SLF4J_PREFIX = "spring.datasource.druid.filter.slf4j";
private static final String FILTER_LOG4J_PREFIX = "spring.datasource.druid.filter.log4j";
private static final String FILTER_LOG4J2_PREFIX = "spring.datasource.druid.filter.log4j2";
private static final String FILTER_COMMONS_LOG_PREFIX = "spring.datasource.druid.filter.commons-log";
private static final String FILTER_WALL_PREFIX = "spring.datasource.druid.filter.wall";
配置示例
spring:
datasource:
url: jdbc:mysql://localhost:3306/db_account
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
druid:
aop-patterns: com.atguigu.admin.* #监控SpringBean
filters: stat,wall # 底层开启功能,stat(sql监控),wall(防火墙)
stat-view-servlet: # 配置监控页功能
enabled: true
login-username: admin
login-password: admin
resetEnable: false
web-stat-filter: # 监控web
enabled: true
urlPattern: /*
exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
filter:
stat: # 对上面filters里面的stat的详细配置
slow-sql-millis: 1000
logSlowSql: true
enabled: true
wall:
enabled: true
config:
drop-table-allow: false
SpringBoot 配置实例
https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter
配置项列表https://github.com/alibaba/druid/wiki/DruidDataSource%E9%85%8D%E7%BD%AE%E5%B1%9E%E6%80%A7%E5%88%97%E8%A1%A8
3.0 整合Mybatis
https://github.com/mybatis
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
配置模式
- 全局配置文件 mybatis-config.xml
- SqlSessionFactory: 自动配置好了
- SqlSession:自动配置了 SqlSessionTemplate 组合了SqlSession
- @Import (AutoConfiguredMapperScannerRegistrar.class)
- @Mapper: 只要我们写的操作MyBatis的接口标准了 @Mapper 就会被自动扫描进来
@EnableConfigurationProperties(MybatisProperties.class) : MyBatis配置项绑定类。
@AutoConfigureAfter({
DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration{
}
@ConfigurationProperties(prefix = "mybatis")
public class MybatisProperties{
}
可以修改配置文件中以 mybatis 开始的所有
# 配置mybatis规则
mybatis:
config-location: classpath:mybatis/mybatis-config.xml #全局配置文件位置
mapper-locations: classpath:mybatis/mapper/*.xml #sql映射文件位置
Mapper接口--->绑定Xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.admin.mapper.AccountMapper">
<!-- public Account getAcct(Long id); -->
<select id="getAcct" resultType="com.atguigu.admin.bean.Account">
select * from account_tbl where id=#{id}
</select>
</mapper>
配置 private Configuration configuration; mybatis.configuration下面的所有, 就是相当于改mybatis全局配置文件中的值
# 配置mybatis规则
mybatis:
# config-location: classpath:mybatis/mybatis-config.xml
mapper-locations: classpath:mybatis/mapper/*.xml
configuration:
map-underscore-to-camel-case: true
可以不写全局配置文件, 所有全局配置文件的配置都放在 configuration 配置项中即可
- 导入 mybatis 官方 starter
- 编写 mapper 接口. 标注 @Mapper 注解
- 编写 sql 映射文件并绑定 mapper 接口
- 在application.yaml中指定Mapper配置文件的位置,以及指定全局配置文件的信息 (建议: 配置在mybatis.configuration)
注解模式
@Mapper
public interface CityMapper {
@Select("select * from city where id=#{id}")
public City getById(Long id);
@Insert("insert into city(`name`,`state`,`country`) values(#{name},#{state},#{country})")
// 把传入的参数的 city 的自增主键赋值
@Options(useGeneratedKeys = true,keyProperty = "id")
public void insert(City city);
}
混合模式
准备一个 Mapper.xml (当 sql 语句比较复杂的时候)
@Mapper
public interface CityMapper {
@Select("select * from city where id=#{id}")
public City getById(Long id);
public void insert(City city);
}
=========== Mapper.xml ===========
<!-- public void insert(City city); -->
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
insert into city(`name`,`state`,`country`) values(#{
name},#{
state},#{
country})
</insert>
最佳实战
- 引入 mybatis-starter
- 配置application.yaml中,指定mapper-location位置
- 编写Mapper接口并标注@Mapper注解
- 简单方法直接注解方式
- 复杂方法编写mapper.xml进行绑定映射
@MapperScan("com.atguigu.admin.mapper")
简化 (标注在主类上),其他的接口就可以不用标注@Mapper
注解
4.0 整合 MyBatis-Plus
CRUD: Create, Retrieve, Update, Delete
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
建议安装 MybatisX 插件
引入 starter
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
自动配置
- MybatisPlusAutoConfiguration 配置类,MybatisPlusProperties 配置项绑定mybatis-plus:xxx 就是对 mybatis-plus 的定制
- SqlSessionFactory 自动配置好。底层是容器中默认的数据源
- mapperLocations 自动配置好的。有默认值。classpath*:/mapper/**/*.xml; 任意包的类路径下的所有mapper文件夹下的所有xml都是sql映射文件. 建议以后sql映射文件放在 mapper 下
- 容器中也自动配置好了 SqlSessionTemplate
- @Mapper 标注的接口也会被自动扫描;建议直接 @MapperScan(“com.atguigu.admin.mapper”) 批量扫描就行
优点:
- 只需要我们的Mapper继承 BaseMapper 就可以拥有crud能力
CURD
@Autowired
UserService userService;
@GetMapping("/user/delete/{id}")
public String deleteUser(@PathVariable("id") Long id,
@RequestParam(value = "pn",defaultValue = "1")Integer pn,
RedirectAttributes ra){
userService.removeById(id);
// RedirectAttributes 是给重定向带参数的s
ra.addAttribute("pn",pn);
return "redirect:/dynamic_table";
}
@GetMapping("/dynamic_table")
public String dynamic_table(@RequestParam(value="pn",defaultValue = "1") Integer pn,Model model){
//从数据库中查出user表中的用户进行展示
//构造分页参数
Page<User> page = new Page<>(pn, 2);
//调用page进行分页
Page<User> userPage = userService.page(page, null);
// userPage.getRecords()
// userPage.getCurrent()
// userPage.getPages()
model.addAttribute("users",userPage);
return "table/dynamic_table";
}
============= MyBatisConfig ==================
// MybatisPlusInterceptor 要实现分页 需要在 MybatisConfig 中添加 MybatisPlusInterceptor
@Bean
public MybatisPlusInterceptor paginationInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
// paginationInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
// paginationInterceptor.setLimit(500);
// 开启 count 的 join 优化,只针对部分 left join
//这是分页拦截器
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
paginationInnerInterceptor.setOverflow(true);
paginationInnerInterceptor.setMaxLimit(500L);
mybatisPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor);
return mybatisPlusInterceptor;
}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService {
}
public interface UserService extends IService<User> {
}
Themeleaf 部分
<thead>
<tr>
<th>#</th>
<th>name</th>
<th>age</th>
<th>email</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr class="gradeX" th:each="user: ${users.records}">
<td th:text="${user.id}"></td>
<td>[[${user.name}]]</td>
<td th:text="${user.age}">Win 95+</td>
<td th:text="${user.email}">4</td>
<td>
<!-- 注意此处如何传递参数以及如何给 {id} 赋值 -->
<a th:href="@{/user/delete/{id}(id=${user.id},pn=${users.current})}" class="btn btn-danger btn-sm" type="button">删除</a>
</td>
</tr>
</tbody>
</table>
<div class="row-fluid">
<div class="span6">
<div class="dataTables_info" id="dynamic-table_info">
当前第[[${users.current}]]页 总计 [[${users.pages}]]页 共[[${users.total}]]条记录
</div>
</div>
<div class="span6">
<div class="dataTables_paginate paging_bootstrap pagination">
<ul>
<li class="prev disabled"><a href="#">← 前一页</a></li>
<!-- 注意此处 numbers.sequence 的用法: 生成 1 到 users.pages 的 <li> -->
<li th:class="${num == users.current?'active':''}" th:each="num:${#numbers.sequence(1,users.pages)}" >
<a th:href="@{/dynamic_table(pn=${num})}">[[${num}]]</a>
</li>
<li class="next disabled"><a href="#">下一页 → </a></li>
</ul>
</div>
</div>
</div>
NOSQL
Redis
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性 (high availability)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
自动配置
- RedisAutoConfiguration 自动配置类。RedisProperties 属性类 --> spring.redis.xxx是对redis的配置
- 连接工厂是准备好的. LettuceConnectionConfiguration、JedisConnectionConfiguration
- 自动注入了RedisTemplate<Object, Object> : xxxTemplate
- 自动注入了StringRedisTemplate; k: v都是String
- key: value
- 底层只要我们使用 StringRedisTemplate、RedisTemplate就可以操作redis
redis 环境搭建
- 阿里云付费 redis. 经典网络
- 申请 redis 的公网连接地址
- 修改白名单, 允许0.0.0.0/0 访问
- 安装 redis 客户端 Another Redis Desktop Manager
RedisTemplate 与 Lettuce
@Test
void testRedis(){
ValueOperations<String, String> operations = redisTemplate.opsForValue();
operations.set("hello","world");
String hello = operations.get("hello");
System.out.println(hello);
}
切换至 jedis
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 导入jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
redis:
host: r-bp1nc7reqesxisgxpipd.redis.rds.aliyuncs.com
port: 6379
password: lfy:Lfy123456
client-type: jedis
jedis:
pool:
max-active: 10
# url: redis://lfy:[email protected]:6379
# lettuce:
# pool:
# max-active: 10
# min-idle: 5
example
创建拦截器并在 AdminWebConfig 注册进容器
@Component
public class RedisUrlCountInterceptor implements HandlerInterceptor {
@Autowired
StringRedisTemplate redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String uri = request.getRequestURI();
//默认每次访问当前uri就会计数+1
redisTemplate.opsForValue().increment(uri);
return true;
}
}
单元测试
JUnit 5
Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库
作为最新版本的JUnit框架,JUnit5与之前版本的Junit框架有很大的不同。由三个不同子项目的几个不同模块组成。
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
JUnit Platform: Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入。
JUnit Jupiter: JUnit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部 包含了一个测试引擎,用于在Junit Platform上运行。
JUnit Vintage: 由于JUint已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4.x, Junit3.x的测试引擎。
注意:
SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖。如果需要兼容junit4需要自行引入 (不能使用junit4的功能 @Test)
JUnit 5’s Vintage Engine Removed from spring-boot-starter-test, 如果需要继续兼容 junit4 需要自行引入vintage
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
现在版本
@SpringBootTest
class Boot05WebAdminApplicationTests {
@Test
void contextLoads() {
}
}
以前
@SpringBootTest + @RunWith(SpringTest.class)
SpringBoot 整合 Junit 以后
- 编写测试方法:@Test 标注 (注意需要使用junit5版本的注解)
- Junit 类具有 Spring 的功能,@Autowired, 比如 @Transactional 标注测试方法,测试完成后自动回滚
JUnit 5 常用注解
JUnit5的注解与JUnit4的注解有所变化
https://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations
- @Test : 表示方法是测试方法. 但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
- @ParameterizedTest : 表示方法是参数化测试,下方会有详细介绍
- @RepeatedTest : 表示方法可重复执行,下方会有详细介绍
- @DisplayName : 为测试类或者测试方法设置展示名称
- @BeforeEach : 表示在每个单元测试之前执行
- @AfterEach : 表示在每个单元测试之后执行
- @BeforeAll : 表示在所有单元测试之前执行
- @AfterAll : 表示在所有单元测试之后执行
- @Tag : 表示单元测试类别,类似于JUnit4中的@Categories
- @Disabled : 表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
- @Timeout : 表示测试方法运行如果超过了指定时间将会返回错误
- @ExtendWith : 为测试类或测试方法提供扩展类引用
/**
*@BootstrapWith(SpringBootTestContextBootstrapper.class)
* @ExtendWith(SpringExtension.class)
*/
//@SpringBootTest
@DisplayName("junit5功能测试类")
public class Junit5Test {
@Autowired
JdbcTemplate jdbcTemplate;
/**
* 测试前置条件
*/
@DisplayName("测试前置条件")
@Test
void testassumptions(){
Assumptions.assumeTrue(false,"结果不是true");
System.out.println("111111");
}
@DisplayName("测试displayname注解")
@Test
void testDisplayName() {
System.out.println(1);
System.out.println(jdbcTemplate);
}
@RepeatedTest(5)
@Test
void test3() {
System.out.println(5);
}
/**
* 规定方法超时时间。超出时间测试出异常
* @throws InterruptedException
*/
@Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
@Test
void testTimeout() throws InterruptedException {
Thread.sleep(600);
}
}
断言 (assertions)
断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证. 这些断言方法都是 org.junit.jupiter.api.Assertions 的静态方法. JUnit 5 内置的断言可以分成如下几个类别:
检查业务逻辑返回的数据是否合理。
所有的测试运行结束以后,会有一个详细的测试报告;
1. 简单断言
用来对单个值进行简单的验证:
方法 | 说明 |
---|---|
assertEquals | 判断两个对象或两个原始类型是否相等 |
assertNotEquals | 判断两个对象或两个原始类型是否不相等 |
assertSame | 判断两个对象引用是否指向同一个对象 |
assertNotSame | 判断两个对象引用是否指向不同的对象 |
assertTrue | 判断给定的布尔值是否为 true |
assertFalse | 判断给定的布尔值是否为 false |
assertNull | 判断给定的对象引用是否为 null |
assertNotNull | 判断给定的对象引用是否不为 null |
2. 数组断言
通过 assertArrayEquals 方法来判断两个对象或原始类型的数组是否相等
3. 组合断言
assertAll 方法接受多个 org.junit.jupiter.api.Executable 函数式接口的实例作为要验证的断言, 可以通过 lambda 表达式很容易的提供这些断言
4. 异常断言
在JUnit4时期, 想要测试方法的异常情况时, 需要用 @Rule 注解的ExpectedException变量还是比较麻烦的. 而JUnit5提供了一种新的断言方式 Assertions.assertThrows(), 配合函数式编程就可以进行使用
5. 超时断言
Junit5还提供了 Assertions.assertTimeout() 为测试方法设置了超时时间
6. 快速失败
通过 fail 方法直接使得测试失败
/**
* 断言:前面断言失败,后面的代码都不会执行
*/
@DisplayName("测试简单断言")
@Test
void testSimpleAssertions() {
int cal = cal(3, 2);
//相等
assertEquals(6, cal, "业务逻辑计算失败");
Object obj1 = new Object();
Object obj2 = new Object();
assertSame(obj1, obj2, "两个对象不一样");
}
@Test
@DisplayName("array assertion")
void array() {
assertArrayEquals(new int[]{
1, 2}, new int[]{
1, 2}, "数组内容不相等");
}
@Test
@DisplayName("组合断言")
void all() {
/**
* 所有断言全部需要成功
*/
assertAll("test",
() -> assertTrue(true && true, "结果不为true"),
() -> assertEquals(1, 2, "结果不是1"));
System.out.println("=====");
}
@DisplayName("异常断言")
@Test
void testException() {
//断定业务逻辑一定出现异常
assertThrows(ArithmeticException.class, () -> {
int i = 10 / 2;
}, "业务逻辑居然正常运行?");
}
@DisplayName("快速失败")
@Test
void testFail(){
//xxxxx
if(1 == 2){
fail("测试失败");
}
}
int cal(int i, int j) {
return i + j;
}
前置条件 (assumptions)
JUnit 5 中的前置条件(assumptions (假设))类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止。前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要
@DisplayName("前置条件")
public class AssumptionsTest {
private final String environment = "DEV";
@Test
@DisplayName("simple")
public void simpleAssume() {
assumeTrue(Objects.equals(this.environment, "DEV"));
assumeFalse(() -> Objects.equals(this.environment, "PROD"));
}
@Test
@DisplayName("assume then do")
public void assumeThenDo() {
assumingThat(
Objects.equals(this.environment, "DEV"),
() -> System.out.println("In DEV")
);
}
}
assumeTrue 和 assumFalse 确保给定的条件为 true 或 false,不满足条件会使得测试执行终止。assumingThat 的参数是表示条件的布尔值和对应的 Executable 接口的实现对象。只有条件满足时,Executable 对象才会被执行;当条件不满足时,测试执行并不会终止
嵌套测试
JUnit 5 可以通过 Java 中的内部类和@Nested 注解实现嵌套测试, 从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach 和 @AfterEach 注解, 而且嵌套的层次没有限制
嵌套测试情况下, 外层的 Test 不能驱动内层的 Before(After) Each/All 之类的方法 提前/之后 运行
@DisplayName("嵌套测试")
public class TestingAStackDemo {
Stack<Object> stack;
@Test
@DisplayName("new Stack()")
void isInstantiatedWithNew() {
new Stack<>();
//嵌套测试情况下,外层的Test不能驱动内层的Before(After)Each/All之类的方法提前/之后运行
assertNull(stack);
}
@Nested
@DisplayName("when new")
class WhenNew {
@BeforeEach
void createNewStack() {
stack = new Stack<>();
}
@Test
@DisplayName("is empty")
void isEmpty() {
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("throws EmptyStackException when popped")
void throwsExceptionWhenPopped() {
assertThrows(EmptyStackException.class, stack::pop);
}
@Test
@DisplayName("throws EmptyStackException when peeked")
void throwsExceptionWhenPeeked() {
assertThrows(EmptyStackException.class, stack::peek);
}
@Nested
@DisplayName("after pushing an element")
class AfterPushing {
String anElement = "an element";
@BeforeEach
void pushAnElement() {
stack.push(anElement);
}
/**
* 内层的Test可以驱动外层的Before(After)Each/All之类的方法提前/之后运行
*/
@Test
@DisplayName("it is no longer empty")
void isNotEmpty() {
assertFalse(stack.isEmpty());
}
@Test
@DisplayName("returns the element when popped and is empty")
void returnElementWhenPopped() {
assertEquals(anElement, stack.pop());
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("returns the element when peeked but remains not empty")
void returnElementWhenPeeked() {
assertEquals(anElement, stack.peek());
assertFalse(stack.isEmpty());
}
}
}
}
参数化测试
参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利。
利用 @ValueSource 等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。
@ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
@NullSource: 表示为参数化测试提供一个null的入参
@EnumSource: 表示为参数化测试提供一个枚举入参
@CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
@MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)
当然如果参数化测试仅仅只能做到指定普通的入参还达不到让我觉得惊艳的地步。让我真正感到他的强大之处的地方在于他可以支持外部的各类入参。如:CSV,YML,JSON 文件甚至方法的返回值也可以作为入参。只需要去实现 ArgumentsProvider 接口,任何外部文件都可以作为它的入参。
@ParameterizedTest
@DisplayName("参数化测试")
@ValueSource(ints = {
1,2,3,4,5})
void testParameterized(int i){
System.out.println(i);
}
@ParameterizedTest
@DisplayName("参数化测试")
@MethodSource("stringProvider")
void testParameterized2(String i){
System.out.println(i);
}
static Stream<String> stringProvider() {
return Stream.of("apple", "banana","atguigu");
}
更多用法见官网
JUnit4 迁移 5
- 注解在 org.junit.jupiter.api 包中,断言在 org.junit.jupiter.api.Assertions 类中, 前置条件在 org.junit.jupiter.api.Assumptions 类中
- 把 @Before 和 @After 替换成 @BeforeEach 和 @AfterEach
- 把 @BeforeClass 和 @AfterClass 替换成 @BeforeAll 和 @AfterAll
- 把 @Ignore 替换成 @Disabled
- 把 @Category 替换成 @Tag
- 把 @RunWith、@Rule 和 @ClassRule 替换成 @ExtendWith
指标监控
SpringBoot Actuator
未来每一个微服务在云上部署以后,我们都需要对其进行监控、追踪、审计、控制等。SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
1.x 和 2.x 的不同
如何使用
- 引入场景
- 访问 http://localhost:8080/actuator/**
- 暴露所有监控信息为 HTTP
management:
endpoints:
enabled-by-default: true #暴露所有端点信息
web:
exposure:
include: '*' #以web方式暴露
- 测试
http://localhost:8080/actuator/beans
http://localhost:8080/actuator/configprops
http://localhost:8080/actuator/metrics
http://localhost:8080/actuator/metrics/jvm.gc.pause
http://localhost:8080/actuator/endpointName/detailPath
可视化(SrpingBootAdminServer)
https://github.com/codecentric/spring-boot-admin
-
创建一个新的SrpingBoot 项目 (只需要选中 web 开发场景)
-
引入starter
<dependency> <groupId>de.codecentric</groupId> <artifactId>spring-boot-admin-starter-server</artifactId> <version>2.3.1</version> </dependency>
-
主类添加注解 @EnableAdminServer 并在配置文件中改变端口(防止和8080冲突)
-
给被监控的项目的pom文件引入依赖
<dependency> <groupId>de.codecentric</groupId> <artifactId>spring-boot-admin-starter-client</artifactId> <version>2.3.1</version> </dependency>
-
给被监控的项目配置 spring.boot.admin.client.url=http: / / localhost:xxxx 并以 web 方式把监控端点打开
boot: admin: client: url: http://localhost:8888 instance: prefer-ip: true #使用ip注册进来 application: name: boot-05-web-admin
Actuator Endpoint
1. 最常使用的端点
ID | 描述 |
---|---|
auditevents | 暴露当前应用程序的审核事件信息。需要一个AuditEventRepository组件 |
beans | 显示应用程序中所有Spring Bean的完整列表 |
caches | 暴露可用的缓存 |
conditions | 显示自动配置的所有条件信息,包括匹配或不匹配的原因 |
configprops | 显示所有@ConfigurationProperties |
env | 暴露Spring的属性ConfigurableEnvironment |
flyway | 显示已应用的所有Flyway数据库迁移。 需要一个或多个 Flyway 组件 |
health | 显示应用程序运行状况信息 |
httptrace | 显示HTTP跟踪信息(默认情况下,最近100个HTTP请求-响应)。需要一个HttpTraceRepository 组件 |
info | 显示应用程序信息 |
integrationgraph | 显示Spring integrationgraph 。需要依赖spring-integration-core |
loggers | 显示和修改应用程序中日志的配置 |
liquibase | 显示已应用的所有Liquibase数据库迁移。需要一个或多个Liquibase 组件 |
metrics | 显示当前应用程序的“指标”信息 |
mappings | 显示所有@RequestMapping 路径列表 |
scheduledtasks | 显示应用程序中的计划任务 |
sessions | 允许从Spring Session支持的会话存储中检索和删除用户会话。需要使用Spring Session的基于Servlet的Web应用程序 |
shutdown | 使应用程序正常关闭。默认禁用 |
startup | 显示由ApplicationStartup 收集的启动步骤数据。需要使用SpringApplication 进行配置BufferingApplicationStartup |
threaddump | 执行线程转储 |
如果您的应用程序是Web应用程序 (Spring MVC,Spring WebFlux或Jersey), 则可以使用以下附加端点:
ID | 描述 |
---|---|
heapdump | 返回hprof 堆转储文件 |
jolokia | 通过HTTP暴露JMX bean(需要引入Jolokia,不适用于WebFlux)。需要引入依赖jolokia-core |
logfile | 返回日志文件的内容(如果已设置logging.file.name 或logging.file.path 属性)。支持使用HTTPRange 标头来检索部分日志文件的内容 |
prometheus | 以Prometheus服务器可以抓取的格式公开指标。需要依赖micrometer-registry-prometheus |
最常用Endpoint
- Health:监控状况
- Metrics:运行时指标
- Loggers:日志记录
2. Health Endpoint
健康检查端点,我们一般用于在云平台,平台会定时的检查应用的健康状况,我们就需要 Health Endpoint 可以为平台返回当前应用的一系列组件健康状况的集合
重要的几点:
- health endpoint返回的结果,应该是一系列健康检查后的一个汇总报告
- 很多的健康检查默认已经自动配置好了,比如:数据库、redis等
- 可以很容易的添加自定义的健康检查机制
3. Metrics Endpoint
提供详细的、层级的、空间指标信息,这些信息可以被pull(主动推送)或者push(被动获取)方式得到
- 通过Metrics对接多种监控系统
- 简化核心Metrics开发
- 添加自定义Metrics或者扩展已有Metrics
4. 管理 Endpoints
开启与禁用 Endpoints
- 默认所有的Endpoint除过shutdown都是开启的
- 需要开启或者禁用某个Endpoint。配置模式为 management.endpoint.<endpointName>.enabled = true
management:
endpoint:
beans:
enabled: true
- 或者禁用所有的 Endpoint 然后手动开启指定的 Endpoint
management:
endpoints:
enabled-by-default: false
endpoint:
beans:
enabled: true
health:
enabled: true
暴露 Endpoints
支持的暴露方式
- HTTP:默认只暴露health和info Endpoint
- JMX:默认暴露所有Endpoint
- 除过health和info,剩下的Endpoint都应该进行保护访问。如果引入SpringSecurity,则会默认配置安全访问规则
ID | JMX | Web |
---|---|---|
auditevents | Yes | No |
beans | Yes | No |
caches | Yes | No |
conditions | Yes | No |
configprops | Yes | No |
env | Yes | No |
flyway | Yes | No |
health | Yes | Yes |
heapdump | N/A | No |
httptrace | Yes | No |
info | Yes | Yes |
integrationgraph | Yes | No |
jolokia | N/A | No |
logfile | N/A | No |
loggers | Yes | No |
liquibase | Yes | No |
metrics | Yes | No |
mappings | Yes | No |
prometheus | N/A | No |
scheduledtasks | Yes | No |
sessions | Yes | No |
shutdown | Yes | No |
startup | Yes | No |
threaddump | Yes | No |
定制Endpoint
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
@Component
public class MyHealthIndicator implements HealthIndicator {
@Override
public Health health() {
int errorCode = check(); // perform some specific health check
if (errorCode != 0) {
return Health.down().withDetail("Error Code", errorCode).build();
}
return Health.up().build();
}
}
构建Health
Health build = Health.down()
.withDetail("msg", "error service")
.withDetail("code", "500")
.withException(new RuntimeException())
.build();
management:
health:
enabled: true
show-details: always #总是显示详细信息。可显示每个模块的状态信息
类名必须为 xxxHealthIndicator 组件名为 xxx
@Component
public class MyComHealthIndicator extends AbstractHealthIndicator {
/**
* 真实的检查方法
* @param builder
* @throws Exception
*/
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
//mongodb。 获取连接进行测试
Map<String,Object> map = new HashMap<>();
// 检查完成
if(1 == 2){
// builder.up(); //健康
builder.status(Status.UP);
map.put("count",1);
map.put("ms",100);
}else {
// builder.down();
builder.status(Status.OUT_OF_SERVICE);
map.put("err","连接超时");
map.put("ms",3000);
}s
builder.withDetail("code",100)
.withDetails(map);
}
}
定制info信息
常用两种方式
编写配置文件
info:
appName: boot-admin
version: 2.0.1
mavenProjectName: @project.artifactId@ #使用@@可以获取maven的pom文件值
mavenProjectVersion: @project.version@
编写 infoContributor
@Component
public class ExampleInfoContributor implements InfoContributor {
@Override
public void contribute(Info.Builder builder) {
builder.withDetail("example",
Collections.singletonMap("key", "value"));
}
}
http://localhost:8080/actuator/info 会输出以上方式返回的所有info信息
定制Metrics信息
SpringBoot 支持自动适配的 Metrics
-
JVM metrics, report utilization of:
- Various memory and buffer pools
- Statistics related to garbage collection
- Threads utilization
- Number of classes loaded/unloaded
-
CPU metrics
-
File descriptor metrics
-
Kafka consumer and producer metrics
-
Log4j2 metrics: record the number of events logged to Log4j2 at each level
-
Logback metrics: record the number of events logged to Logback at each level
-
Uptime metrics: report a gauge for uptime and a fixed gauge representing the application’s absolute start time
-
Tomcat metrics (
server.tomcat.mbeanregistry.enabled
must be set totrue
for all Tomcat metrics to be registered) -
Spring Integration metrics
-
Spring Integration metrics
增加定制 Metrics
class MyService{
Counter counter;
public MyService(MeterRegistry meterRegistry){
counter = meterRegistry.counter("myservice.method.running.counter");
}
public void hello() {
// counter 计数 + 1
counter.increment();
}
}
//也可以使用下面的方式
@Bean
MeterBinder queueSize(Queue queue) {
return (registry) -> Gauge.builder("queueSize", queue::size).register(registry);
}
定制Endpoint
@Component
@Endpoint(id = "container")
public class DockerEndpoint {
@ReadOperation
public Map getDockerInfo(){
return Collections.singletonMap("info","docker started...");
}
@WriteOperation
private void restartDocker(){
System.out.println("docker restarted....");
}
}
场景:开发ReadinessEndpoint来管理程序是否就绪,或者LivenessEndpoint来管理程序是否存活;
当然,这个也可以直接使用 https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-kubernetes-probes
原理解析
Profile 功能
为了方便多环境适配,springboot 简化了 profile 功能
appliacation-profile功能
- 默认配置文件 application.yaml 任何时候都会加载
- 指定环境配置文件 application-{env}.yaml
- 激活指定环境
- 配置文件激活 (默认配置文件写: spring.profiles.active={env})
- 命令行激活:java -jar xxx.jar –spring.profiles.active=prod --person.name=haha
- 修改配置文件的任意值, 命令行优先
- 默认配置与环境配置同时生效
- 同名配置项,profile 配置优先
@Value
@Value("{xxx}") xxx可以是来自配置文件也可以是环境变量 (MAVEN_HOME) 或 系统属性
因为都在容器中
ConfigurableApplicationContext run = SpringApplication.run(Boot09FeaturesProfileApplication.class, args);
ConfigurableEnvironment environment = run.getEnvironment();
Map<String, Object> systemEnvironment = environment.getSystemEnvironment();
Map<String, Object> systemProperties = environment.getSystemProperties();
System.out.println(systemEnvironment);
System.out.println(systemProperties);
@Profile条件装配功能
可以标在类上或方法上
@Configuration(proxyBeanMethods = false)
// production 环境生效, 类才生效
@Profile("production")
public class ProductionConfiguration {
// ...
}
profile分组
====== 默认配置文件 =====
spring.profiles.group.production[0]=proddb
spring.profiles.group.production[1]=prodmq
使用:--spring.profiles.active=production 激活
外部化配置
https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config
-
Default properties (specified by setting
SpringApplication.setDefaultProperties
). -
@PropertySource
annotations on your@Configuration
classes. Please note that such property sources are not added to theEnvironment
until the application context is being refreshed. This is too late to configure certain properties such aslogging.*
and `spring.main. which are read before refresh begins. -
Config data (such as
application.properties
files) -
A
RandomValuePropertySource
that has properties only inrandom.*
-
OS environment variables.
-
Java System properties (
System.getProperties()
). -
JNDI attributes from
java:comp/env
. -
ServletContext` init parameters
-
ServletConfig
init parameters. -
Properties from
SPRING_APPLICATION_JSON
(inline JSON embedded in an environment variable or system property). -
Command line arguments.
-
properties
attribute on your tests. Available on@SpringBootTest
and the test annotations for testing a particular slice of your application. -
@TestPropertySource
annotations on your tests. -
Devtools global settings properties in the
$HOME/.config/spring-boot
directory when devtools is active.
指定环境优先,外部优先,后面的可以覆盖前面的同名配置项
常用外部配置源
常用:Java属性文件、YAML文件、环境变量、命令行参数
配置文件查找位置
(1) classpath 根路径
(2) classpath 根路径下config目录
(3) jar包当前目录
(4) jar包当前目录的config目录
(5) /config子目录的直接子目录
指定环境优先,外部优先,后面的可以覆盖前面的同名配置项
配置文件加载顺序
-
当前jar包内部的application.properties和application.yml
-
当前jar包内部的application-{profile}.properties 和 application-{profile}.yml
-
引用的外部jar包的application.properties和application.yml
-
引用的外部jar包的application-{profile}.properties 和 application-{profile}.yml
以上内容后面的可以覆盖前面的同名配置项
自定义starter
starter 启动原理
- starter-pom引入 autoconfigurer 包
- autoconfigure包中配置使用 META-INF/spring.factories 中 EnableAutoConfiguration 的值,使得项目启动加载指定的自动配置类
- 编写自动配置类 xxxAutoConfiguration -> xxxxProperties
- @Configuration
- @Conditional
- @EnableConfigurationProperties
- @Bean
- …
引入starter — xxxAutoConfiguration — 容器中放入组件 ---- 绑定xxxProperties ---- 配置项
自定义过程
atguigu-hello-spring-boot-starter (启动器)
- pom 引入自动配置包
atguigu-hello-spring-boot-starter-autoconfigure (自动配置包, 用SpringInitializer 创建, 不勾选任何场景, 并删除不必要的依赖如 Junit…)
-
写一个组件 (如Service), 默认不放入容器中
-
写一个xxxProperties.java
@ConfigurationProperties("atguigu.hello") public class HelloProperties { }
-
写一个xxxAutoConfiguration.java
@Configuration @EnableConfigurationProperties(HelloProperties.class) // 默认HelloProperties放在容器中 public class HelloServiceAutoConfiguration{ @ConditionalOnMissingBean(HelloService.class) @Bean public HelloService helloService(){ HelloService helloService = new HelloService(); return helloService; } }
-
创建/resources/META-INF/spring.factories
# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.atguigu.hello.auto.HelloServiceAutoConfiguration
先打包 自动配置包 到本地仓库 (clean + install) 然后打包 启动器
SpringBoot原理
Spring原理 (Spring注解)、SpringMVC原理、自动配置原理、SpringBoot原理
-
创建 SpringApplication
-
保存一些信息
-
判定当前应用的类型. Servlet ClassUtils
-
bootstrappers:初始启动引导器 (List<Bootstrapper>): 去spring.factories文件中找 org.springframework.boot.Bootstrapper
public interface Bootstrapper { /** * Initialize the given {@link BootstrapRegistry} with any required registrations. * @param registry the registry to initialize */ void intitialize(BootstrapRegistry registry); }
-
找 ApplicationContextInitializer; 去 spring.factories 找 ApplicationContextInitializer
- List<ApplicationContextInitializer<?>> initializers
-
找 ApplicationListener: 应用监听器. 去 spring.factories 找 ApplicationListener
-
List<ApplicationListener<?>> listeners
-
-
-
运行 SpringApplication
- StopWatch 记录应用的启动时间
- 创建引导上下文(Context环境)createBootstrapContext()
- 获取到所有之前的 bootstrappers 挨个执行 intitialize() 来完成对引导启动器上下文环境设置
- 让当前应用进入headless模式。java.awt.headless (自力更生模式)
- 获取所有 RunListener (运行监听器)【为了方便所有Listener进行事件感知】
- getSpringFactoriesInstances 去 spring.factories 找SpringApplicationRunListener
- 遍历 SpringApplicationRunListener 调用 starting 方法
- 相当于通知所有感兴趣系统正在启动过程的人,项目正在 starting
- 保存命令行参数: ApplicationArguments
- 准备环境 prepareEnvironment()
- 返回或者创建基础环境信息对象: StandardServletEnvironment
- 配置环境信息对象
- 读取所有的配置源的配置属性值
- 绑定环境信息
- 监听器调用 listener.environmentPrepared();通知所有的监听器当前环境准备完成
- 创建 IOC 容器 createApplicationContext()
- 根据项目类型 (Servlet) 创建容器
- 当前会创建 AnnotationConfigServletWebServerApplicationContext
- 准备 ApplicationContext IOC 容器的基本信息 prepareContext()
- 保存环境信息
- IOC容器的后置处理流程
- 应用初始化器: applyInitializers
- 遍历所有的 ApplicationContextInitializer 。调用 initialize 来对 IOC 容器进行初始化扩展功能
- 遍历所有的 listener 调用 contextPrepared: EventPublishRunListenr; 通知所有的监听器contextPrepared
- 所有的监听器 调用 contextLoaded. 通知所有的监听器 contextLoaded
- 刷新IOC容器: refreshContext()
- 创建容器中的所有组件(Spring 注解开发课程有详细讲解)
- 容器刷新完成后工作: afterRefresh() 方法内为空
- 所有监听 器 调用 listeners.started(context); 通知所有的监听器 started
- 调用所有runners: callRunners()
- 获取容器中的 ApplicationRunner
- 获取容器中的 CommandLineRunner
- 合并所有runner并且按照 @Order 进行排序
- 遍历所有的runner: 调用 run 方法
- 如果以上步骤有异常将调用 Listener 的 failed()
- 调用所有监听器的 running 方法 listeners.running(context) 通知所有的监听器 running
- running如果有问题, 继续通知 failed, 调用所有 Listener 的 failed; 通知所有的监听器 failed
Application Events and Listeners
https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-application-events-and-listeners
ApplicationContextInitializer
ApplicationListener
SpringApplicationRunListener
ApplicationRunner 与 CommandLineRunner
public class MySpringApplicationRunListener implements SpringApplicationRunListener {
private SpringApplication application;
// 不写会报错
public MySpringApplicationRunListener(SpringApplication application, String[] args){
this.application = application;
}
......
}
@Order(2)
@Component
public class MyCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("MyCommandLineRunner....run....");
}
}