SpringBoot2核心技术-核心功能【第二篇】

该文档是:SpringBoot教程(文章部分内容转自尚硅谷笔记)

博客连接:https://www.loveuluo.cn

日期:2021-01-17

3️⃣ 数据访问

1、SQL

1.1、数据源的自动配置-HikariDataSource

(1)导入JDBC场景

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>

image.png

为什么导入JDBC场景,官方不导入驱动?官方不知道我们接下要操作什么数据库。

数据库版本最好和驱动版本对应:

<!--这是parent中的默认版本仲裁:<mysql.version>8.0.22</mysql.version>-->
想要修改版本
1、直接依赖引入具体版本(第一种办法:maven的就近优先依赖原则)
2、重新声明版本(maven的属性的就近优先原则)

<!-- 第二种办法,重新声明版本 -->    
<properties>
    <java.version>1.8</java.version>
    <mysql.version>5.1.49</mysql.version>
</properties>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <!-- 第一种办法,写死版本,直接修改 -->    
    <!-- <version>5.1.49</version> -->
</dependency>

(2)分析自动配置

  • 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

      spring:
        jdbc:
          template: 
            query-timeout: 3 #可以配置jdbctemplate的查询超时时间等等
    • @Bean@Primary JdbcTemplate;容器中有这个组件 可以直接@Autowired注入JdbcTemplate进行使用
  • JndiDataSourceAutoConfiguration: jndi的自动配置
  • XADataSourceAutoConfiguration: 分布式事务相关的

(3)修改配置项-配置连接

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/db_account
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver

(4)测试

@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);
    }
}

1.2、使用Druid数据源

(1)druid官方github地址

https://github.com/alibaba/druid

整合第三方技术的两种方式

  • 自定义
  • 找starter

(2)自定义方式

①如何用自定义配置druid?

这是原生的配置文件,怎么把他改造成springboot要用的呢?:

<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的用途包括:

  • 提供监控信息展示的html页面
  • 提供监控信息的JSON API

StatFilter用于:

  • 统计监控信息;如SQL监控、URI监控

image-20210117160019581

image-20210117160425337

②实现上边的所有

引入依赖:

</dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.17</version>
</dependency>

把原生的配置转换到SpringBoot的application.yaml中配置:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/db_account
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver

    filters: stat,wall,  #开启的功能
    max-active: 12

配置类:

@Configuration
public class MyDataSourceConfig {

    // 默认的自动配置是判断容器中没有才会配@ConditionalOnMissingBean(DataSource.class)
    @ConfigurationProperties("spring.datasource") //找到SpringBoot的配置文件 读取前缀为spring.datasource的自动导入
    @Bean //自己弄了个DataSource并放入容器中,所以此时会用我们所写的
    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的监控页功能
     * @return
     */
    @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)使用官方starter方式简化自定义操作

引入druid-starter:

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.17</version>
        </dependency>

分析自动配置:

  • 扩展配置项 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.Luo*  #监控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

1.3、整合MyBatis操作

https://github.com/mybatis

第一步都是得引入starter。

SpringBoot官方的Starter:spring-boot-starter-*

第三方的: *-spring-boot-starter

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.4</version>
</dependency>

image.png

(1)配置模式

原理讲解:

  • 全局配置文件
  • 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

测试用的数据库表和实体类:

image-20210117185012983

@ToString
@Data
public class Account {
    private Long accountId;
    private String accountName;
    private Double money;
}

在application.yaml中配置mybatis有关配置:

可以不写mybatis全局配置文件直接在application.yaml所有mybatis相关的配置,也可以创建一个mybatis全局配置文件在application.yaml指定它的位置,选一使用。

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/ssm
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver

# 配置mybatis规则
mybatis:
#  config-location: classpath:mybatis-config.xml  可以指定mybatis全局配置文件位置。
  mapper-locations: classpath:mapper/*.xml        #指定sql映射文件位置
  # 也可以直接在SpringBoot配置文件中直接配置相关配置,一旦在这里配置了相关配置之后,就不能再指定mybatis全局配置文件,二者选一用。
  # 这里我们直接在SpringBoot配置文件中用configuration:配置相关信息
  configuration:
    # 这代表启动用mybatis驼峰规则,也就是说数据库中的例如account_name,会变成对应实体类的accountName,_n会被理解成大写的N
    map-underscore-to-camel-case: true

编写mapper接口。标注@Mapper注解:

@Mapper//告诉SpringBoot这是mybatis接口
public interface AccountMapper {
    public List<Account> findAllAccount();
}

编写sql映射文件并绑定mapper接口:

image-20210117185218977

<?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.luo.mapper.AccountMapper">
    <!--    对应public List<Account> findAllAccount();方法  -->
    <select id="findAllAccount" resultType="com.luo.bean.Account">
        select * from  account
    </select>
</mapper>

测试类:

@Slf4j
@SpringBootTest
class MybatisApplicationTests {

    @Autowired
    AccountMapper accountMapper;

    @Test
    void contextLoads() {
        List<Account> allAccount = accountMapper.findAllAccount();
        for (Account account : allAccount) {
            System.out.println(account);
        }
    }
}

测试结果:

image-20210117185324976

编写mapper接口。标注@Mapper注解:

(2)纯注解模式

在application.yaml中配置mybatis有关配置:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/ssm
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver
      
# 配置mybatis规则
mybatis:
  # 也可以直接在SpringBoot配置文件中直接配置相关配置,一旦在这里配置了相关配置之后,就不能再指定mybatis全局配置文件,二者选一用。
  # 这里我们直接在SpringBoot配置文件中用configuration:配置相关信息
  configuration:
    # 这代表启动用mybatis驼峰规则,也就是说数据库中的例如account_name,会变成对应实体类的accountName,_n会被理解成大写的N
    map-underscore-to-camel-case: true

编写mapper接口。标注@Mapper注解:

@Mapper//告诉SpringBoot这是mybatis接口
public interface AccountMapper {
    @Select("select * from  account")
    public List<Account> findAllAccount();
}

(3)注解配置混合模式

  • 引入mybatis-starter
  • 配置application.yaml中,指定mapper-location位置即可
  • 编写Mapper接口并标注@Mapper注解
  • 简单方法直接注解方式
  • 复杂方法编写mapper.xml进行绑定映射
  • 也可以用@MapperScan(basePackages = "com.luo.mapper")简化,其他的接口就可以不用标注@Mapper注解,不过还是建议在每个mapper接口上标注@Mapper
@MapperScan(basePackages = "com.luo.mapper")
@SpringBootApplication
public class MybatisApplication {

    public static void main(String[] args) {
        SpringApplication.run(MybatisApplication.class, args);
    }

}

1.4、整合 MyBatis-Plus 完成CRUD

(1)什么是MyBatis-Plus

MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

mybatis plus 官网

建议安装 MybatisX 插件

(2)整合MyBatis-Plus

        <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能力

(3)CRUD功能

    @GetMapping("/user/delete/{id}")
    public String deleteUser(@PathVariable("id") Long id,
                             @RequestParam(value = "pn",defaultValue = "1")Integer pn,
                             RedirectAttributes ra){

        userService.removeById(id);

        ra.addAttribute("pn",pn);
        return "redirect:/dynamic_table";
    }


    @GetMapping("/dynamic_table")
    public String dynamic_table(@RequestParam(value="pn",defaultValue = "1") Integer pn,Model model){
        //表格内容的遍历
//        response.sendError
//     List<User> users = Arrays.asList(new User("zhangsan", "123456"),
//                new User("lisi", "123444"),
//                new User("haha", "aaaaa"),
//                new User("hehe ", "aaddd"));
//        model.addAttribute("users",users);
//
//        if(users.size()>3){
//            throw new UserTooManyException();
//        }
        //从数据库中查出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";
    }
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService {


}

public interface UserService extends IService<User> {

}

2、NoSQL

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings)散列(hashes)列表(lists)集合(sets)有序集合(sorted sets) 与范围查询, bitmapshyperloglogs地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication)LUA脚本(Lua scripting)LRU驱动事件(LRU eviction)事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

2.1、Redis自动配置

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

image.png

自动配置:

  • RedisAutoConfiguration 自动配置类。RedisProperties 属性类 --> spring.redis.xxx是对redis的配置
  • 连接工厂是准备好的。LettuceConnectionConfiguration、JedisConnectionConfiguration
  • 自动注入了RedisTemplate<Object, Object> : xxxTemplate;
  • 自动注入了StringRedisTemplate;k:v都是String
  • key:value
  • 底层只要我们使用 StringRedisTemplate、RedisTemplate就可以操作redis

redis环境搭建

1、阿里云按量付费redis。经典网络

2、申请redis的公网连接地址

3、修改白名单 允许0.0.0.0/0 访问

2.2、RedisTemplate与Lettuce

    @Test
    void testRedis(){
        ValueOperations<String, String> operations = redisTemplate.opsForValue();

        operations.set("hello","world");

        String hello = operations.get("hello");
        System.out.println(hello);
    }

3.3、切换至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>
spring:
  redis:
      host: r-bp1nc7reqesxisgxpipd.redis.rds.aliyuncs.com
      port: 6379
      password: lfy:Lfy123456
      client-type: jedis
      jedis:
        pool:
          max-active: 10

4️⃣ 单元测试

1、JUnit5 的变化

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的测试引擎。

img

注意:

SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖。如果需要兼容junit4需要自行引入

JUnit 5 默认移除了Vintage Engine从 spring-boot-starter-test,如果想要继续兼容junit4需要自行引入vintage**

<!--如果需要继续使用junit4,可以引入以下-->
<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>

如何使用junit5:

image.png

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
</dependency>

以前用法:

@SpringBootTest + @RunWith(SpringTest.class)

现在用法:

@SpringBootTest // 使用SpringBoot的测试驱动来测试(使用SpringBoot中的容器功能),如果不需要用到SpringBoot的功能和容器等等,只需要@Test注解就能进行测试
class Boot05WebAdminApplicationTests {
    
    @Test
    void contextLoads() {

    }
}

SpringBoot整合Junit以后非常方便:

  • 编写测试方法:@Test标注(注意需要使用junit5版本的注解)
  • Junit类具有Spring的功能,@Autowired、比如 @Transactional 标注测试方法,测试完成后自动回滚

2、JUnit5常用注解

JUnit5的注解与JUnit4的注解有所变化

https://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations

  • @Test :表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
  • @ParameterizedTest :表示方法是参数化测试,下方会有详细介绍
  • @RepeatedTest :表示方法可重复执行,下方会有详细介绍。如果是@RepeatedTest(5)这样写,代表这个方法点击运行会重复测试5次。
  • @DisplayName :为测试类或者测试方法设置展示名称
  • @BeforeEach :表示在每个单元测试之前执行
  • @AfterEach :表示在每个单元测试之后执行
  • @BeforeAll :表示在所有单元测试之前执行
  • @AfterAll :表示在所有单元测试之后执行
  • @Tag :表示单元测试类别,类似于JUnit4中的@Categories
  • @Disabled :表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
  • @Timeout :表示测试方法运行如果超过了指定时间将会返回错误
  • @ExtendWith :为测试类或测试方法提供扩展类引用

@Test@SpringBootTest@DisplayName@Disabled@BeforeEach@AfterEach@BeforeAll@AfterAll用法测试:

@SpringBootTest// 使用SpringBoot的测试驱动来测试(使用SpringBoot中的容器功能)
/* @SpringBootTest注解中有这两个注解:
   @BootstrapWith(SpringBootTestContextBootstrapper.class)
   @ExtendWith(SpringExtension.class) 代表下边这些测试是使用Spring的整个测试驱动来进行测试的                               */
@DisplayName("junit5功能测试类")
public class Junit5Test {

    @Autowired //例如这个自动注入,需要加上@SpringBootTest后才能使用
    JdbcTemplate jdbcTemplate;
    
    @DisplayName("测试方法1")
    @Test
    void Test01(){
        System.out.println(1);
    }
    @Test
    @DisplayName("测试方法2")
    void Test02(){
        System.out.println(2);
    }
    @Test
    @Disabled
    @DisplayName("测试方法3")
    void Test03(){
        System.out.println(3);
    }

    @BeforeEach
    void testBeforeEach() {
        System.out.println("测试就要开始了...");
    }
    @AfterEach
    void testAfterEach() {
        System.out.println("测试结束了...");
    }
    @BeforeAll
    //所有方法之后执行的方法需要加上static
    static void testBeforeAll() {
        System.out.println("所有测试就要开始了...");
    }
    @AfterAll
    //所有方法之前执行的方法需要加上static
    static void testAfterAll() {
        System.out.println("所有测试以及结束了...");
    }


}

测试结果:

image-20210118101907471

@Timeout用法测试:

/**
 * 规定方法超时时间。超出时间测试出异常
 *
 * @throws InterruptedException
 */
@Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
@Test
void testTimeout() throws InterruptedException {
    Thread.sleep(600);
}

测试结果:

image-20210118103701821

@RepeatedTest用法测试:

@RepeatedTest(5)
@Test
void test3() {
    System.out.println(5);
}

测试结果:

image-20210118103820790

3、断言(assertions)

断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是 org.junit.jupiter.api.Assertions 的静态方法。JUnit 5 内置的断言可以分成如下几个类别:

检查业务逻辑返回的数据是否合理。

可以一次性测试所有的测试,结束后会有一个详细的测试报告:

image-20210118110237017

3.1、简单断言

用来对单个值进行简单的验证。如:

方法说明
assertEquals判断两个对象或两个原始类型是否相等
assertNotEquals判断两个对象或两个原始类型是否不相等
assertSame判断两个对象引用是否指向同一个对象
assertNotSame判断两个对象引用是否指向不同的对象
assertTrue判断给定的布尔值是否为 true
assertFalse判断给定的布尔值是否为 false
assertNull判断给定的对象引用是否为 null
assertNotNull判断给定的对象引用是否不为 null

测试代码:

int cal(int i, int j) {
    return i + j;
}

/**
 * 断言:前面断言失败,后面的代码都不会执行
 */
@DisplayName("测试简单断言")
@Test
void testSimpleAssertions() {
    int cal = cal(3, 2);
    //assertEquals(判断是否相等) = 第一个参数是你期望的值,第二个是真实的值,第三个是错误提示信息
    assertEquals(6, cal, "业务逻辑计算失败");
    //==========================如果前边的断言失败,后边的代码就不会执行==========================
    Object obj1 = new Object();
    Object obj2 = new Object();
    //assertSame(测试两个东西是不是同一个对象) = 第一个参数是你期望的值,第二个是业务逻辑真实的对象,第三个是错误提示信息
    assertSame(obj1, obj2, "两个对象不一样");

}

测试结果:

image-20210118104327596

3.2、数组断言

测试代码:

@Test
@DisplayName("数组断言")
void array() {
    //判断两个数组的内容相不相等
    assertArrayEquals(new int[]{2, 1}, new int[]{1, 2}, "数组内容不相等");
}

测试结果:

image-20210118104639340

3.3、组合断言

assertAll 方法接受多个 org.junit.jupiter.api.Executable 函数式接口的实例作为要验证的断言,可以通过 lambda 表达式很容易的提供这些断言

测试代码:

@Test
@DisplayName("组合断言")
void all() {
    //所有断言全部需要成功才能执行下去
    assertAll("test",
            //assertTrue = 希望结果是true
            () -> assertTrue(1==1 && 2==1, "结果不为true"),
            () -> assertEquals(1, 2, "结果不是1"));
    System.out.println("=====");
}

测试结果:

image-20210118105130919

3.4、异常断言

在JUnit4时期,想要测试方法的异常情况时,需要用@Rule注解的ExpectedException变量还是比较麻烦的。而JUnit5提供了一种新的断言方式Assertions.assertThrows() ,配合函数式编程就可以进行使用。

测试代码:

@DisplayName("异常断言")
@Test
void testException() {

    //断定业务逻辑一定出现某某异常,如果没有出现异常则测试错误,出现异常则测试通过
    assertThrows(ArithmeticException.class, () -> {
        int i = 10 / 2;
    }, "业务逻辑居然正常运行?");
}

测试结果:

image-20210118105543720

3.5、超时断言

Junit5还提供了Assertions.assertTimeout() 为测试方法设置了超时时间

测试代码:

@Test
@DisplayName("超时测试")
public void timeoutTest() {
    //如果测试方法时间超过1s将会异常 ,左边是指定时间,右边是方法执行花费了多长时间
    Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(1500));
}

测试结果:

image-20210118105859737

3.6、快速失败

测试代码:

@DisplayName("快速失败")
@Test
void testFail(){
    //架设前边一堆方法执行....,到了此处如果if判断成立直接测试失败
    if(2 == 2){
        fail("测试失败");
    }
}

测试结果:

image-20210118110214191

4、前置条件(assumptions)

JUnit 5 中的前置条件(assumptions【假设】)类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止。前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要。

测试代码:

@DisplayName("前置条件")
public class AssumptionsTest {
     private final String environment = "DEV";
 
     @Test
     @DisplayName("simple")
     public void simpleAssume() {
        //assumeTrue
        assumeTrue(Objects.equals(this.environment, "DEV"));
        assumeFalse(() -> Objects.equals(this.environment, "PROD"));
     }

     @Test
     @DisplayName("assume then do")
     public void assumeThenDo() {
        //assumingThat
        assumingThat(
           Objects.equals(this.environment, "DEV"),
           () -> System.out.println("In DEV")
        );
     }
}

assumeTrue 和 assumFalse 确保给定的条件为 true 或 false,不满足条件会使得测试执行终止。assumingThat 的参数是表示条件的布尔值和对应的 Executable 接口的实现对象。只有条件满足时,Executable 对象才会被执行;当条件不满足时,测试执行并不会终止。

5、嵌套测试

JUnit 5 可以通过 Java 中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach 和@AfterEach 注解,而且嵌套的层次没有限制。

测试代码:

@DisplayName("A stack")
class TestingAStackDemo {

    Stack<Object> stack;

    @Test
    @DisplayName("is instantiated with new Stack()")
    void isInstantiatedWithNew() {
        new 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
            @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());
            }
        }
    }
}

6、参数化测试

参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利。

利用@ValueSource等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。

@ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型

@NullSource: 表示为参数化测试提供一个null的入参

@EnumSource: 表示为参数化测试提供一个枚举入参

@CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参

@MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)

当然如果参数化测试仅仅只能做到指定普通的入参还达不到让我觉得惊艳的地步。让我真正感到他的强大之处的地方在于他可以支持外部的各类入参。如:CSV,YML,JSON 文件甚至方法的返回值也可以作为入参。只需要去实现ArgumentsProvider接口,任何外部文件都可以作为它的入参。

测试代码:

@ParameterizedTest //代表当前方法不是一个普通的测试,它是参数化测试
@DisplayName("参数化测试1")
@ValueSource(ints = {1,2,3,4,5}) //参数从哪来...这5个值会默认传入到:方法的参数int i中
void testParameterized(int i){
    System.out.println(i);
}

@ParameterizedTest
@DisplayName("参数化测试2")
@MethodSource("stringProvider") //参数从哪来...从方法中来,需要指定方法名
void testParameterized2(String i){
    System.out.println(i);
}

static Stream<String> stringProvider() {
    return Stream.of("apple", "banana","shit");
}

测试结果:

image-20210118112546173

image-20210118112602682

7、迁移指南

如何把junit4迁移到junit5,在进行迁移的时候需要注意如下的变化:

  • 注解在 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。

5️⃣ 指标监控

1、开启SpringBoot Actuator

1.1、简介

未来每一个微服务在云上部署以后,我们都需要对其进行监控、追踪、审计、控制等。SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

image.png

1.2、1.x与2.x的不同

image.png

1.3、如何使用

  • 引入场景
  • 访问 http://localhost:8080/actuator/**
  • 暴露所有监控信息为HTTP(默认是只有healthinfo才能在HTTP访问到也就是浏览器,别的只能在Java监视和管理控制台中进行查看)
management:
  # endpoints代表设置所有端点
  endpoints:
    enabled-by-default: true #开启所有的端点 (默认值)
    web:
      exposure:
        include: '*'  #暴露所有监控信息为HTTP (以web方式暴露)
  • 可以单个单个设置端点
management:
  # endpoints代表设置所有端点
  endpoints:
    enabled-by-default: false #关闭开启所有端点,单独开启或配置
    web:
      exposure:
        include: '*'  #暴露所有监控信息为HTTP (以web方式暴露)

  endpoint:
    health:
      show-details: always #显示详细信息
      enabled: true #单独开启health端点
    info:
      enabled: true #单独开启info端点
    beans:
      enabled: true #单独开启beans端点
    metrics:
      enabled: true #单独开启metrics端点
  • HTTP访问测试

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
。。。。。。

1.4、可视化

可视化平台:https://github.com/codecentric/spring-boot-admin

image-20210118185939683

2、Actuator的端点

2.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.namelogging.file.path属性)。支持使用HTTPRange标头来检索部分日志文件的内容。
prometheus以Prometheus服务器可以抓取的格式公开指标。需要依赖micrometer-registry-prometheus

最常用的Endpoint(端点):

  • Health:监控状况
  • Metrics:运行时指标
  • Loggers:日志记录

2.2、常用端点之Health 端点

健康检查端点,我们一般用于在云平台,平台会定时的检查应用的健康状况,我们就需要Health Endpoint可以为平台返回当前应用的一系列组件健康状况的集合。

重要的几点:

  • health endpoint返回的结果,应该是一系列健康检查后的一个汇总报告
  • 很多的健康检查默认已经自动配置好了,比如:数据库、redis等
  • 可以很容易的添加自定义的健康检查机制

image.png

2.3、常用端点之Metrics 端点

提供详细的、层级的、空间指标信息,这些信息可以被pull(主动推送)或者push(被动获取)方式得到;

  • 通过Metrics对接多种监控系统
  • 简化核心Metrics开发
  • 添加自定义Metrics或者扩展已有Metrics

image.png

2.4、管理端点

(1)开启与禁用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

(2)暴露Endpoints

支持的暴露方式

  • HTTP:默认只暴露healthinfo Endpoint
  • JMX(Java监视和管理控制台中):默认暴露所有Endpoint
  • 除过health和info,剩下的Endpoint都应该进行保护访问。如果引入SpringSecurity,则会默认配置安全访问规则
IDJMXWeb
auditeventsYesNo
beansYesNo
cachesYesNo
conditionsYesNo
configpropsYesNo
envYesNo
flywayYesNo
healthYesYes
heapdumpN/ANo
httptraceYesNo
infoYesYes
integrationgraphYesNo
jolokiaN/ANo
logfileN/ANo
loggersYesNo
liquibaseYesNo
metricsYesNo
mappingsYesNo
prometheusN/ANo
scheduledtasksYesNo
sessionsYesNo
shutdownYesNo
startupYesNo
threaddumpYesNo

3、自定义 端点(Endpoint)

3.1、自定义 Health 信息

场景:

image-20210118165642489

(1)方法一:继承抽象类

创建一个类继承抽象类AbstarctHealthIndicator,这个类的后缀名必须是HealthIndicator:

@Component//把组件放到容器中
public class MyComHealthIndicator extends AbstractHealthIndicator {

    /**
     * 真实的检查方法
     * @param builder
     * @throws Exception
     */
    @Override
    protected void doHealthCheck(Health.Builder builder) throws Exception {
        Map<String,Object> map = new HashMap<>();
        // 检查完成
        if(1 == 1){ //条件是自己写的,这里是示范
//          builder.up(); //表示健康
            //更详细的返回信息
            builder.status(Status.UP); //Status.UP表示健康
            //模拟健康要返回的数据.放入map集合中
            map.put("count",1);
            map.put("ms",100);
        }else {
//          builder.down(); /表示宕机
            //更详细的返回信息
            builder.status(Status.OUT_OF_SERVICE); //Status.OUT_OF_SERVICE表示不能服务
            //模拟宕机要返回的数据.放入map集合中
            map.put("err","连接超时");
            map.put("ms",3000);
        }
        
        //代表把这些详细信息也返回
        builder.withDetail("code",100)
                .withDetails(map);
    }
}

用1=1测试健康返回的结果:

image-20210118170619598

如果是不健康会返回什么的结果:

image-20210118170832970

(2)方法二:实现接口

@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();

3.2、自定义 info 信息

当前应用的详细信息,详细信息默认是什么也没有的。

(1)方法一:编写配置文件

info:
  appName: boot-admin
  version: 2.0.1
  mavenProjectName: @project.artifactId@  #不写死,使用@@可以来获取maven的pom文件值
  mavenProjectVersion: @project.version@

image-20210118171513355

(2)方法二:编写InfoContributor

@Component//把组件放到容器中
public class AppInfoInfoContributor implements InfoContributor {

    @Override
    public void contribute(Info.Builder builder) {
        builder.withDetail("msg","你好")
                .withDetail("hello","atguigu")
                .withDetails(Collections.singletonMap("world","666600"));
    }
}

image-20210118171858757

3.3、自定义 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 to true for all Tomcat metrics to be registered)
  • Spring Integration metrics

(1)增加定制Metrics

@Service
//可以做一个统计的次数指标,查看当前Service中的saveCity方法被调用了几次
public class CityServiceImpl implements CityService {

    @Autowired
    CityMapper cityMapper;

    //把参数定义到外边
    Counter counter;
    
    //默认一个有参构造 其定义的参数会自动中容器中拿到同名的组件:meterRegistry(指标的注册中心)
    public CityServiceImpl(MeterRegistry meterRegistry){
        //.counter() 计数类的指标,里边填的是指标名字
        counter = meterRegistry.counter("cityService.saveCity.count");
    }

    public void saveCity(City city) {
        counter.increment(); //每调用一次这个方法,用increment()增加一个次数
        cityMapper.insert(city);

    }
}

image-20210118173138771

image-20210118173226642

3.4、自定义 端点(Endpoint)

@Component
@Endpoint(id = "myservice")
//http://localhost:8080/actuator/myservice
public class MyServiceEndPoint {

    //端点的读操作
    @ReadOperation
    public Map getDockerInfo(){
        return Collections.singletonMap("dockerInfo","docker started.....");
    }

    //端点的写操作
    @WriteOperation
    public void stopDocker(){
        System.out.println("docker stopped.....");
    }

}

image-20210118185150051

场景:开发ReadinessEndpoint来管理程序是否就绪,或者LivenessEndpoint来管理程序是否存活;

当然,这个也可以直接使用 https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-kubernetes-probes

6️⃣原理解析

1、Profile功能

为了方便多环境适配,springboot简化了profile功能。

1.1、application-profile功能

  • 默认配置文件 application.yaml;任何时候都会加载
  • 指定环境配置文件 application-{env}.yaml
  • 激活指定环境
    • 配置文件激活
    • 命令行激活:java -jar xxx.jar --spring.profiles.active=prod --person.name=haha
      • 修改配置文件的任意值,命令行优先
  • 默认配置与环境配置同时生效
  • 同名配置项,profile配置优先

1.2、@Profile条件装配功能

@Configuration(proxyBeanMethods = false)
@Profile("production")
public class ProductionConfiguration {

    // ...

}

1.3、profile分组

spring.profiles.group.production[0]=proddb
spring.profiles.group.production[1]=prodmq

使用:--spring.profiles.active=production  激活

2、外部化配置

https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config

  1. Default properties (specified by setting SpringApplication.setDefaultProperties).
  2. @PropertySource annotations on your @Configuration classes. Please note that such property sources are not added to the Environment until the application context is being refreshed. This is too late to configure certain properties such as logging.* and spring.main.* which are read before refresh begins.
  3. Config data (such as application.properties files)
  4. A RandomValuePropertySource that has properties only in random.*.
  5. OS environment variables.
  6. Java System properties (System.getProperties()).
  7. JNDI attributes from java:comp/env.
  8. ServletContext init parameters.
  9. ServletConfig init parameters.
  10. Properties from SPRING_APPLICATION_JSON (inline JSON embedded in an environment variable or system property).
  11. Command line arguments.
  12. properties attribute on your tests. Available on @SpringBootTest and the test annotations for testing a particular slice of your application.
  13. @TestPropertySource annotations on your tests.
  14. Devtools global settings properties in the $HOME/.config/spring-boot directory when devtools is active.

2.1、外部配置源

常用:Java属性文2、配置文件查找位置

(1) classpath 根路径

(2) classpath 根路径下config目录

(3) jar包当前目录

(4) jar包当前目录的config目录

(5) /config子目录的直接子目录

2.2、配置文件查找位置

(1) classpath 根路径

(2) classpath 根路径下config目录

(3) jar包当前目录

(4) jar包当前目录的config目录

(5) /config子目录的直接子目录

2.3、配置文件加载顺序:

  1.  当前jar包内部的application.properties和application.yml
  2.  当前jar包内部的application-{profile}.properties 和 application-{profile}.yml
  3.  引用的外部jar包的application.properties和application.yml
  4.  引用的外部jar包的application-{profile}.properties 和 application-{profile}.yml

2.4、指定环境优先,外部优先,后面的可以覆盖前面的同名配置项、YAML文件环境变量命令行参数

3、自定义starter

3.1、starter启动原理

  • starter-pom引入 autoconfigure

image-20210118201114066

  • autoconfigure包中配置使用 META-INF/spring.factoriesEnableAutoConfiguration 的值,使得项目启动加载指定的自动配置类
  • 编写自动配置类 xxxAutoConfiguration -> xxxxProperties
    • @Configuration
    • @Conditional
    • @EnableConfigurationProperties
    • @Bean
    • ......

引入starter --- xxxAutoConfiguration --- 容器中放入组件 ---- 绑定xxxProperties ---- 配置项

3.2、自定义starter

(1)编写自定义starter

  • luo-hello-spring-boot-starter(场景启动器一般只用于当前场景有多少个依赖,把依赖引进去,没有任何别的代码)
  • luo-hello-spring-boot-starter-autoconfigure(真正的自动配置功能由自动配置包完成)

创建一个空工程,创建两个项目:

image-20210119115718654

luo-hello-spring-boot-starter项目的pom.xml中引入autoconfigure这个项目:

<groupId>com.Luo</groupId>
<artifactId>Luo-hello-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>

<dependencies>
    <!--别人只要一引入场景启动器,配置包也进来了-->
    <dependency>
        <groupId>com.Luo</groupId>
        <artifactId>luo-hello-spring-boot-starter-autoconfigure</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
</dependencies>

autoconfigure项目的pom.xml:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.2</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.Luo</groupId>
<artifactId>luo-hello-spring-boot-starter-autoconfigure</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>luo-hello-spring-boot-starter-autoconfigure</name>
<description>Demo project for Spring Boot</description>

<properties>
    <java.version>1.8</java.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>

</dependencies>

autoconfigure项目的HelloService类:

//默认不要放在容器中,用自动配置类来完成这个事情
public class HelloService {

    //传入人名打招呼
    public String sayHello(String userName){
        //@EnableConfigurationProperties开启绑定功能,并且把HelloProperties放到容器中,此时可以自动注入进来
        @Autowired
        HelloProperties helloProperties;

        return helloProperties.getPrefix() + ":" + userName + "》" + helloProperties.getSuffix();
    }
}

autoconfigure项目的属性类:

//属性类(绑定配置文件里边以Luo.hello开头的配置)
@ConfigurationProperties(prefix = "Luo.hello")
public class HelloProperties {

    private String prefix;
    private String suffix;
    //get set方法
}

autoconfigure项目的自动配置类:

//Configuration代表这是一个配置类
@Configuration
//开启属性文件绑定功能 这样的话HelloProperties自动就会跟配置文件Luo.hello开头的进行绑定。绑定完了还会默认把HelloProperties放到容器中
@EnableConfigurationProperties(HelloProperties.class)
public class HelloServiceAutoConfiguration {

    //当调用这个的人他没有自己配置HelloService进入容器中的话 这里才会把HelloService这个组件放入容器中
    //这个注解写到方法上而不是类上是为了防止@EnableConfigurationProperties注解也失效掉。
    @ConditionalOnMissingBean(HelloService.class) 
    @Bean
    public HelloService helloService(){
        //这个HelloService跟一个属性文件绑定,是自动注入的
        HelloService helloService = new HelloService();
        return helloService;
    }

}

核心关键!自动配置类不可能默认生效,生效的规则是检查META-INF/spring.factoriesEnableAutoConfiguration 的值指定的这些类才会在项目启动时自动加载。在autoconfigure项目下resources文件下创建一个META-INF文件夹,文件夹下创建spring.factories文件:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
# 项目一启动要加载哪个包哪个自动配置类
com.luo.hello.HelloServiceAutoConfiguration

(2)使用自定义starter

场景启动器依赖于自动配置包,所以先给自动配置包安装到maven本地仓库中:

image-20210119122841023

把我们的场景启动器也安装到maven本地仓库:

image-20210119123039088

随便找一个项目把我们的场景启动器引入进去测试一下是否可以用:

<!--我们自己写的场景启动器-->
<dependency>
    <groupId>com.Luo</groupId>
    <artifactId>Luo-hello-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

准备一个HelloController进行测试:

//自动配置类中把HelloService放入了容器中,这里可以直接自动注入了
@Autowired
HelloService helloService;

@GetMapping("/hello")
public String sayHello(){
    String s = helloService.sayHello("张三");
    return s;
}

编写SpringBoot主配置文件application.properties:

Luo.hello.prefix=LOVEULUO
Luo.hello.suffix=6666

最终结果:

image-20210119134703813

(3)总结最终的调用过程

引入自定义starter ==》
starter帮我们引入了他的自动配置场景 ==》
自动配置场景一启动他在spring.factories中说你给我加载HelloServiceAutoConfiguration ==》
此时自动配置类就加载了,自动配置类一加载,自动配置类上写着当容器中没有HelloService组件的时候给我们自动放入HelloService组件 ==》
同时自动配置类会给我们开启属性文件绑定功能,这样的话HelloProperties自动就会跟配置文件中Luo.hello开头的进行绑定,并且绑定上之后会把HelloProperties放入容器中 ==》
HelloSerivce组件从容器中中自动注入了HelloProperties==》
所以我们只需要在配置文件中配置相关属性就能使用了。当然我们也可以自己注册一个HelloService进入容器中,这样自动配置类中的HelloService就不会放入容器中。

4、SpringBoot原理

Spring原理【Spring注解】、SpringMVC原理、自动配置原理、SpringBoot原理

4.1、SpringBoot启动过程

  • 创建 SpringApplication

    • 保存一些信息。
    • 判定当前应用的类型。ClassUtils。Servlet
    • bootstrappers:初始启动引导器(List<Bootstrapper>):去spring.factories文件中找 org.springframework.boot.Bootstrapper
    • ApplicationContextInitializer;去spring.factories找 ApplicationContextInitializer

      • List<ApplicationContextInitializer<?>> initializers

    ApplicationListener ;应用监听器。spring.factoriesApplicationListener

    • List<ApplicationListener<?>> listeners
  • 运行 SpringApplication

    • StopWatch
    • 记录应用的启动时间
    • 创建引导上下文(Context环境)createBootstrapContext()

      • 获取到所有之前的 bootstrappers 挨个执行 intitialize() 来完成对引导启动器上下文环境设置
    • 让当前应用进入headless模式。java.awt.headless
    • 获取所有 RunListener(运行监听器)【为了方便所有Listener进行事件感知】

      • getSpringFactoriesInstances 去spring.factoriesSpringApplicationRunListener.
    • 遍历 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
public interface Bootstrapper {

    /**
     * Initialize the given {@link BootstrapRegistry} with any required registrations.
     * @param registry the registry to initialize
     */
    void intitialize(BootstrapRegistry registry);

}

image.png

image.png

image.png

@FunctionalInterface
public interface ApplicationRunner {

    /**
     * Callback used to run the bean.
     * @param args incoming application arguments
     * @throws Exception on error
     */
    void run(ApplicationArguments args) throws Exception;

}
@FunctionalInterface
public interface CommandLineRunner {

    /**
     * Callback used to run the bean.
     * @param args incoming main method arguments
     * @throws Exception on error
     */
    void run(String... args) throws Exception;

}
最后修改:2021 年 01 月 19 日 01 : 56 PM
如果觉得我的文章对你有用,请随意赞赏