SpringBoot基础

SpringBoot

概述

SpringBoot简介

SpringBoot提供了一种快速使用Spring的方式,基于约定优于配置的思想,可以让开发人员不必在配置与逻辑业务之间进行思维的切换,全身心投入到逻辑业务的代码编写中,从而大大提高开发效率,一定程度上缩短了项目周期。2014年4月SpringBoot 1.0.0发布,是Spring的顶级项目之一

Spring的缺点

  • 配置繁琐

    虽然Spring的组件代码是轻量级的,但它的配置却是重量级的。一开始Spring用XML配置,而且是很多XML配置,Spring2.5引入了基于注解的组件扫描,这消除了大量针对应用程序自身组件的显式XML配置。Spring3.0引入了基于Java的配置,这是一种类型安全的可重构配置方式,可以代替XML。

    所有这些配置都代表了开发时的损耗,因为在思考Spring特性配置和解决业务问题之间需要进行思维切换,所以编写配置挤占了编写应用程序逻辑的时间,和所有框架一样,Spring实用,但它要求的回报也不少

  • 依赖繁琐

    项目的依赖管理也是一件耗时耗力的事情。在环境搭建时,需要分析要导入哪些库的坐标,而且还需要分析导入与之有依赖关系的其他库的坐标,一旦选错了依赖的版本,随之而来的不兼容问题就会严重阻碍项目的开发进度

SpringBoot的功能

  • 自动配置

    SpringBoot的自动配置是一个运行时(应用程序启动时)的过程,考虑了众多因素,才决定Spring配置应该用哪个、不该用哪个。

  • 起步依赖(依赖传递)

    起步依赖本质上是一个Maven项目对象模型(POM),定义了对其他库的传递依赖,这些东西加在一起即支持某项功能。简单来说,起步依赖就是将具备各种功能的坐标打包到一起,并提供一些默认的功能

  • 辅助功能

    提供了一些大型项目中常见的非功能性特性,如嵌入式服务器、安全、指标、健康检测、外部配置等

SpringBoot并不是对Spring功能上的增强,而是提供了一种快速使用Spring的方式

SpringBoot快速入门

Maven项目手动引入SpringBoot依赖方式

  1. 引入父坐标

    1
    2
    3
    4
    5
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.6.RELEASE</version>
    </parent>
  2. 引入web依赖

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
  3. 引入插件

    1
    2
    3
    4
    5
    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <version>2.2.6.RELEASE</version>
    </plugin>
  4. 创建SpringBootApplication核心启动类

    1
    2
    3
    4
    5
    6
    @SpringBootApplication
    public class HelloApplication {
    public static void main(String[] args) {
    SpringApplication.run(HelloApplication.class, args);
    }
    }
  5. 创建Controller进行测试

    1
    2
    3
    4
    5
    6
    7
    @RestController
    public class HelloController {
    @RequestMapping("/hello")
    public String hello(@RequestParam(value = "name", defaultValue = "tao") String name) {
    return "hello " + name;
    }
    }
  6. 访问默认的8080端口测试/hello返回的结果(不带name参数为hello tao,否则为hello name值

补充:要返回JSP页面需要引入tomcat-embed-jasper依赖,并且此依赖不能与servlet-api依赖冲突

1
2
3
4
5
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>

利用Spring Initialzr快速创建SpringBoot项目

image-20210909213009923

SpringBoot起步依赖原理

  • 在spring-boot-starter-parent中定义了各种技术的版本信息,组合了一套最优搭配的版本

  • 在各种starter中,定义了该功能所需要的坐标合集,其中大部分版本信息来自于父工程

  • 我们的工程继承于parent,引入starter后,通过依赖传递,就可以简单方便的获取需要的jar包,并且不会导致版本冲突等问题

SpringBoot配置文件

配置文件分类

SpringBoot是基于约定的,所以很多的配置都有默认值,如果想使用自己的配置替换默认配置的话,就可以使用application.properties或者application.yml(也可以是yaml后缀)进行配置

  • properties方式

    1
    server.port=8080
  • yml方式

    1
    2
    server:
    port: 8080

总结:

  • SpringBoot提供了2种配置文件的类型:properties和yml
  • 默认的配置文件名称:application
  • 在同一级目录下的优先级为:properties > yml > yaml
  • 自定义配置文件名:
    • 后缀类型也要是properties或yml
    • 启动参数--spring.config.name=自定义配置文件名(参数的自定义配置文件名不需要加类型后缀)

注意:IDEA对.yaml的配置文件没有语法提示的解决办法:

  • 在IDEA中设置模块的Facet: image-20211116223539830
  • 如果配置文件名称不是application可能导致添加配置时点不了ok按钮,需要设置上图spring.config.name

yaml

YAML全程是YAML Ain’t Markup Language。YAML是一种直观的能够被电脑识别的数据序列化格式,并且容易被人类阅读,容易和脚本语言交互的,可以被支持YAML库的不同编程语言程序导入。比如C/C++,Ruby,Python,Java,Perl,C#,PHP等。YML文件是以数据为核心的,比传统的xml方式更加简洁。扩展名可以为.yml或者.yaml

  • properties方式:

    1
    2
    server.port=8080
    server.address=127.0.0.1
  • xml方式:

    1
    2
    3
    4
    <server>
    <port>8080</port>
    <address>127.0.0.1</address>
    </server>
  • yml方式:

    1
    2
    3
    server:
    port: 8080
    address: 127.0.0.1

YAML基本语法:

  • 大小写敏感
  • 数据值前面必须有空格
  • 使用缩进表示层级关系
  • 缩进时不允许使用Tab键,只允许使用空格(各个系统Tab对应的空格数目可能不同,导致层次混乱)
  • 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
  • #表示注释,从这个字符一直到行尾都会被解析器忽略

YAML数据格式

  • 对象(map):键值对集合

    1
    2
    3
    4
    5
    6
    person:
    name: zhangsan
    # 行内写法
    person: {name: zhangsan}

    map: {key1: value1,key2: value2}
  • 数组:一组按次序排序的值

    1
    2
    3
    4
    5
    address:
    - beijing
    - shanghai
    # 行内写法
    address: [beijing,shanghai]
  • 对象集合:利用List<User> users接收

    1
    2
    3
    4
    5
    6
    # 两个user对象
    users:
    - name: 张三
    age: 20
    - name: 李四
    age: 22
  • 纯量:单个的、不可再分的值

    1
    2
    msg1: 'hello \ n world' # 单引号或者不加任何引号忽略转义字符
    msg2: "hello \n world" # 双引号识别转义字符

YAML参数引用

${配置项名}

读取配置文件内容

@Value

直接取出配置文件中某个完整字段的值赋值给成员属性,此类必须为Spring组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Value("${name}")
private String name;

@Value("${person.name}")
private String personName;
@Value("${person.age}")
private String personAge;

# 读取数组可以直接用List或Set接收
@Value("${address[0]}")
private String address1;
@Value("${address[1]}")
private String address2;

@Value("${msg1}")
private String msg1;
@Value("${msg2}")
private String msg2;

冷知识:

  • @Value("#{beanName}"):可用于注入一个bean,类似于@Resource, 表示SpEl表达式通常用来获取bean的属性,或者调用bean的某个方法。当然还有可以表示常量

  • @Value(${配置文件的属性}):获取配置文件中的属性值

  • 它俩可以结合使用:比如:@Value("#{'${spring.hhh}'.split(',')}")是一个结合使用的案例,可以把配置文件的属性键spring.hhh对应的值解析为List注入

    1
    2
    spring:
    hh: 1,2,3,a
    1
    2
    @Value("#{'${spring.hh}'.split(',')}")
    private List list;
  • @value(${配置文件的属性:默认值}):用于给此配置属性设定默认值

@ConfigurationProperties

功能 @ConfigurationProperties @Value
松散绑定 支持 仅有限支持驼峰和横杠转换
SpEL 不支持 支持
元数据支持(提示) 支持 不支持
JSR 303数据校验 支持 不支持
复杂类型封装 支持 不支持
  • 作用:读取配置文件的配置项绑定到此配置类的成员属性上。

  • 说明:此注解需要配置一个prefix“前缀”参数,例如:@ConfigurationProperties(prefix = "person"),表示读取此前缀下的内容到此注解修饰的类中。如果不配置前缀,会导致读取到第一个符合此类中成员属性名的字段时就赋值。

  • 注册此配置类为Bean

    • 通过给配置类打上@Component把配置类注册为Spring的Bean
  • 通过在@SpringBootApplication注解所在的启动类上使用**@EnableConfigurationProperties**(参数为配置类的class对象)把配置类注册为Spring的Bean。

  • 获取配置类的Bean(注入)

    • 属性注入:通过@Autowired注入配置类的Bean到成员属性中: image-20210914010646993

    • 构造器注入:把配置类作为参数赋值给成员属性 image-20210914010703668

    • Setter注入:利用Setter给成员属性赋值: image-20210914170108938

      以上这三种方式都可以把配置类注册到Spring容器中,并且通过构造方法注入参数时可以省略@Autowired,但进行属性注入时不能注入到final属性中,所以不推荐使用字段(属性)注入

  • 搭配@Bean使用

    • @ConfigurationProperties注解也可以用在@Bean修饰的public方法上,给此方法返回值所产生的Bean的成员属性赋值,并且可以省略赋值语句完成自动赋值,只需无脑return new 需要的Bean()就行了

      这种方式在读取配置文件给第三方依赖的Bean赋值时特别有用,例如配置不含起步依赖的druid连接池时利用此方法产生DruidDataSource的Bean并且赋值一些从配置文件中读取到的配置给此Bean

  • 注意

    • 需要提供Setter方法,也可以利用lombok的@Data注解
    • 也可以通过@ConstructorBinding进行构造器绑定,此时需要全参构造,并且产生Bean不能通过常规的@Bean或@Component或@Import,而需要@EnableConfigurationProperties(参数为配置类的class对象)注解来注入配置类的Bean或利用@ConfigurationPropertiesScan配置属性扫描
1
2
3
4
5
6
7
8
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
private String name;
private Integer age;
private String[] address;
//需要Getter/Setter
}

可以读取以下yml文件的内容:

1
2
3
4
person:
name: ${name}
age: 20
address: [beijing,shanghai]

建议配置注解处理器以提供配合@ConfigurationProperties注解编写配置文件的语法提示:

松散绑定

对于类中的成员属性名,例如:firstName

配置文件中可选的松散绑定名称:firstName、first-name、first_name、FIRST_NAME

但松散匹配注解@ConfigurationProperties和@Value的参数不能出现大写子母(实际配置项可以松散绑定)

  • 引入注解处理器的依赖:

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
    </dependency>
  • ctrl+F9重新构建项目,使注解处理器生效即可在编写配置文件时触发@ConfigurationProperties语法提示,此操作会编译产生一个META-INF/spring-configuration-metadata.json文件,语法提示依赖此文件

数据校验需要配合JSR 303依赖,然后给类打上@Validated注解,再配合具体注解进行校验。内部类需要@Valid

Environment

  • 注入Environment对象

    1
    2
    @Autowired
    private Environment environment;
  • 使用此对象的getProperty(String s)方法获取s对应的值

    1
    2
    3
    4
    environment.getProperty("name"));
    environment.getProperty("person2.age"));
    environment.getProperty("address[0]"));
    environment.getProperty("msg1"));

profile

我们在开发SpringBoot应用时,通常同一套程序会被安装到不同的环境,例如:开发、测试、生产等。其中数据库地址、服务器端口等配置都不同,如果每次打包时都修改配置会非常麻烦。profile就是来进行动态配置切换的。

  • profile配置方式

    • 多profile文件方式(properties或yml都可以)

      • 创建application-环境名1.properties
      • 创建application-环境名2.properties
      • 创建application-环境名3.properties
      • 在application.properties中指定spring.profiles.active=环境名即可使用上面创建的多个文件的某一个
    • yml多文档方式

      • 在一个yml文件中利用---分割多个配置文档,并利用spring.config.activate.on-profile指定环境名

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        ---
        server:
        port: 8191
        spring:
        config:
        activate:
        on-profile: dev
        ---
        server:
        port: 8192
        spring:
        config:
        activate:
        on-profile: pro
        ---
        server:
        port: 8193
        spring:
        config:
        activate:
        on-profile: test
      • 继续在此yml文件中指定使用哪个环境:

        1
        2
        3
        4
        ---
        spring:
        profiles:
        active: test #启用上面的test环境

      值得注意的是:yml配置的优先级低于properties,即使在yml文档中指定环境名也会先找此环境名的properties文件

  • profile激活方式

    • 配置文件

      • properties文件激活profile:spring.profiles.active=环境名
      • yml文档激活profile:spring.profiles.active: 环境名
    • 虚拟机参数

      • 在IDEA中配置VM参数: image-20210909235501673
      • image-20210909235542637
      • 虚拟机选项配置为:-Dspring.profiles.active=环境名(优先级高于配置文件的方式)
    • 命令行参数

      • 在IDEA中配置程序参数: image-20210909235501673
      • image-20210909235830212
      • 程序参数配置为:--spring.profiles.active=环境名(优先级最高)

      在控制台利用命令行启动java -jar ./springboot项目jar包 --spring.profiles.active=环境名

内部配置加载顺序

  1. file:./config/:当前项目的/config目录下
  2. file:./:当前项目的根目录
  3. classpath:/config/:classpath的/config目录(默认classpath为resources目录)
  4. classpath:/:classpath的根目录(默认classpath为resources目录)

优先级从高到低,所有配置文件都会被读取,但会以高优先级的文件属性为准

外部配置加载顺序

由于maven的package命令不会打包当前项目的pom.xml所在层级的文件,所以要手动指定外部配置

  1. jar包所在目录下的config目录下的application.properties会被自动读取,优先级最高
  2. jar包所在目录下的application.properties会被自动读取,优先级次之

也可以利用java -jar ./项目jar包 --配置项名=配置值在执行时指定配置

还可以利用java -jar ./项目jar包 --spring.config.location=配置文件的绝对地址 引用磁盘上的配置

SpringBoot整合其他框架

SpringBoot整合Junit

  1. 引入test的起步依赖和junit坐标

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
    </dependency>
  2. 编写测试类,打上@RunWith(SpringRunner.class)注解和@SpringBootTest注解

    1
    2
    @RunWith(SpringRunner.class)
    @SpringBootTest
  3. 为测试方法打上@Test注解

注意:如果需要注入java目录下的类,需要保证test目录下的目录层次与引导类的包结构层次一致,否则需要给@SpringBootTest注解指定classes属性为引导类的class

1
@SpringBootTest(classes = SpringBootJunitApplication.class)

如果目录层次一致可以省略classes属性,但要保证类位于SpringBootApplication注解类所在的包结构层次及子包层次,因为默认扫描

SpringBoot整合Redis

  1. 引入Redis起步依赖

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
  2. 注入使用RedisTemplate模板,调用相应的方法

    1
    2
    @Autowired
    private RedisTemplate redisTemplate;

SpringBoot整合MyBatis

  1. 引入mybatis起步依赖,添加mysql驱动

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.0</version>
    </dependency>
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
    </dependency>
  2. 编写DataSource和MyBatis相关配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # 配置datasource
    spring:
    datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql:///db3
    username: root
    password: 199988
    # 配置MyBatis的XML
    mybatis:
    mapper-locations: classpath:mapper/*Mapper.xml #Mapper映射文件的位置
    #别名扫描的包,例如User类会被扫描为user或User的别名
    type-aliases-package: com.taoyyz.springbootmybatis.domain
  3. 定义表和实体类

  4. 编写dao/mapper文件或注解开发

    • 注解开发:此时的UserMapper是一个Spring容器中的Bean,可以直接调用方法

      1
      2
      3
      4
      5
      @Mapper
      public interface UserMapper {
      @Select("select * from user")
      List<User> findAll();
      }
    • XML开发:需要在XML中编写SQL,UserXmlMapper也是一个Spring Bean

      • 编写Mapper接口

        1
        2
        3
        4
        @Mapper
        public interface UserXmlMapper {
        List<User> findAll();
        }
      • 配置Mapper.xml

        1
        2
        3
        4
        5
        6
        7
        8
        9
        <?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.taoyyz.springbootmybatis.mapper.UserXmlMapper">
        <select id="findAll" resultType="user">
        select * from user
        </select>
        </mapper>

SpringBoot整合Druid

  1. 引入Druid起步依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.21</version>
    </dependency>
  2. 配置SpringBoot核心配置文件

    • 方式1:

      1
      2
      3
      4
      5
      6
      7
      spring:
      datasource:
      url: jdbc:mysql://120.79.141.53:3307/mimile?useSSL=false
      username: root
      password: 123
      driver-class-name: com.mysql.jdbc.Driver
      type: com.alibaba.druid.pool.DruidDataSource
    • 方式2:

      1
      2
      3
      4
      5
      6
      7
      spring:
      datasource:
      druid:
      url: jdbc:mysql://120.79.141.53:3307/mimile?useSSL=false
      username: root
      password: 123
      driver-class-name: com.mysql.jdbc.Driver

SpringBoot原理分析

SpringBoot自动配置

Conditional条件判断

image-20210910235324694

Conditional是Spring 4.0增加的条件判断功能,通过这个功能可以实现选择性的创建Bean操作

用法

  1. @Conditional注解打在产生Bean的方法上,常配合@Bean注解实现条件判断创建Bean

  2. @Conditional注解的参数属性需要一个实现了Condition函数式接口的条件类的Class字节码对象

  3. 在这个Condition函数式接口实现类中重写boolean matches(条件context参数,元注解metadata参数)方法

  4. 根据此matches()方法返回的结果进行条件判断,为true就加载Bean,否则false不加载

  5. SpringBoot提供了一些@ConditionalOnXxx的注解用于条件判断

    例如:@ConditionalOnProperty(name = "tjj", havingValue = "22")可以判断配置文件是否有tjj=22的属性

@Conditional扩展注解 作用(条件判断)
@ConditionalOnJava 系统的Java版本是否符合要求
@ConditionalOnBean 容器中存在指定的Bean
@ConditionalOnMissingBean 容器中不存在指定的Bean
@ConditionalOnExpression 满足SpEL表达式
@ConditionalOnClass 系统中有指定的类
@ConditionalOnMissingClass 系统中没有指定的类
@ConditionalOnSingleCandidate 容器中只有一个指定的Bean,或这个Bean为首选Bean
@ConditionalOnProperty 系统中指定的属性是否有指定的值
@ConditionalOnResource 类路径下是否存在指定资源文件
@ConditionalOnWebApplication 当前是web环境
@ConditionalOnNotWebApplication 当前不是web环境
@ConditionalOnJndi JNDI存在指定项

切换内置Web服务器

SpringBoot的web环境默认使用tomcat作为内置服务器,提供了4种内置服务器供我们选择

image-20210911002022854

  • 切换到Jetty

    1. 在pom.xml排除spring-boot-starter-web起步依赖中的spring-boot-starter-tomcat依赖

    2. 引入jetty的依赖

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <exclusions>
      <!--排除掉tomcat的依赖-->
      <exclusion>
      <artifactId>spring-boot-starter-tomcat</artifactId>
      <groupId>org.springframework.boot</groupId>
      </exclusion>
      </exclusions>
      </dependency>
      <!--引入jetty的依赖-->
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-jetty</artifactId>
      </dependency>

@Enable*注解

SpringBoot中提供了很多Enable开头的注解,这些注解都是用于动态启用某些功能的。而底层原理是使用@Import导入一些配置类实现Bean的动态加载

  • 默认情况下@SpringBootApplication只会扫描当前包及其子包的Bean,所以其他模块的Bean获取不到

  • 获取其他模块的Bean的几种方式:

    • 使用@ComponentScan指定扫描的包

      1
      @ComponentScan("com.taoyyz.config")
    • 使用@Import注解加载类,被@Import导入的类都会被Spring容器创建和管理

      1
      @Import(UserConfiguration.class)
    • 对@Import注解进行封装

      • 在其他模块中创建一个EnableXxx的注解,在此注解中利用@Import加载类,只需要在使用时@EnableXxx即可

        1
        2
        3
        4
        5
        6
        @Target({ElementType.TYPE})
        @Retention(RetentionPolicy.RUNTIME)
        @Documented
        @Import(UserConfiguration.class)
        public @interface EnableUser {
        }
      • 在@SpringBootApplication配置类上调用@EnableUser注解即可

@Import注解

@Enable*底层依赖于@Import注解导入一些类,使用@Import导入的类会被Spring加载到Ioc容器中,而@Import提供4种用法

导入Bean

1
2
@Import(User.class) //通过字节码Import的Bean在容器中的名称为全限定名com.taoyyz.domain.User
@Import(Role.class) //同理,在容器中的名称为全限定名com.taoyyz.domain.Role

导入配置类

  • 创建配置类

    1
    2
    3
    4
    5
    6
    public class UserConfiguration {
    @Bean("user")
    public User getUser() {
    return new User();
    }
    }
  • 导入配置类

    1
    @Import(UserConfiguration.class) //导入配置类的字节码,从字节码中获得Bean,Bean的名称为@Bean规定的

导入ImportSelector实现类。一般用于加载配置文件中的类

  • 创建ImportSelector实现类

    1
    2
    3
    4
    5
    6
    public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
    return new String[]{"com.taoyyz.domain.User", "com.taoyyz.domain.Role"};
    }
    }
  • 导入此实现类

    1
    @Import(MyImportSelector.class)

导入ImportBeanDefinitionRegistrar实现类

  • 创建ImportBeanDefinitionRegistrar实现类

    1
    2
    3
    4
    5
    6
    7
    public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
    registry.registerBeanDefinition("user",beanDefinition);
    }
    }
  • 导入此实现类

    1
    @Import(MyImportBeanDefinitionRegistrar.class)

@EnableAutoConfiguration注解

由于@SpringBootApplication注解被@EnableAutoConfiguration注解修饰,而@EnableAutoConfiguration注解又导入了AutoConfigurationImportSelector的字节码文件

image-20210911151122395
  • AutoConfigurationImportSelector类的getCandidateConfigurations()方法会读取spring.factories配置文件,根据@Conditional条件加载配置类初始化Bean
  • 只有满足了@Conditional条件的Bean才会被初始化

自定义starter

  • 要求:自定义redis-starter,当导入redis坐标时,SpringBoot自动创建Jedis的Bean

  • 实现步骤:

    • 创建redis-spring-boot-autoconfigure模块,初始化Jedis的Bean,并定义META-INF/spring.factories文件

      • 创建RedisAutoConfiguration配置类,用于通过配置信息产生Bean

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        @Configuration
        @EnableConfigurationProperties(RedisProperties.class)
        @ConditionalOnClass(Jedis.class)
        public class RedisAutoConfiguration {
        /**
        * 提供Jedis的Bean
        */
        @Bean("jedis")
        @ConditionalOnMissingBean(name = "jedis") //如果没有叫做jedis的bean才加载
        public Jedis getJedis(RedisProperties props) {
        System.out.println("config的bean");
        return new Jedis(props.getHost(), props.getPort());
        }
        }
      • 创建RedisProperties配置读取类,读取application.properties或yml文件的属性赋值给配置成员属性

        1
        2
        3
        4
        5
        6
        @ConfigurationProperties(prefix = "redis") //绑定以redis开头的properties配置,赋值给成员属性
        @Data //利用lombok产生Getter/Setter
        public class RedisProperties {
        private String host = "localhost";
        private int port = 6379;
        }
      • 在resources目录下创建META-INF目录,编辑spring.factories文件

        1
        2
        org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
        com.taoyyz.config.RedisAutoConfiguration
      • 引入jedis依赖

        1
        2
        3
        4
        5
        6
        <!--引入jedis依赖-->
        <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>2.9.0</version>
        </dependency>
    • 创建redis-spring-boot-starter模块,依赖redis-spring-boot-autoconfigure模块

      1
      2
      3
      4
      5
      6
      <!--引入autoconfigure的依赖-->
      <dependency>
      <groupId>com.taoyyz</groupId>
      <artifactId>redis-spring-boot-autoconfigure</artifactId>
      <version>0.0.1-SNAPSHOT</version>
      </dependency>
    • 在测试模块中引入自定义的redis-starter起步依赖,并测试是否能根据依赖引入自动配置获得Bean操作redis

      1
      2
      3
      4
      5
      6
      <!--引入自定义的redis起步依赖-->
      <dependency>
      <groupId>com.taoyyz</groupId>
      <artifactId>redis-spring-boot-starter</artifactId>
      <version>0.0.1-SNAPSHOT</version>
      </dependency>

SpringBoot监听机制

Java监听机制

SpringBoot的监听机制是对Java提供的监听机制的封装

Java的事件监听机制定义了以下几个角色:

  • 事件:Event,继承java.util.EventObject类的对象
  • 事件源:Srouce,任意Object对象
  • 监听器:Listener,实现了java.util.EventListener接口的对象

SpringBoot监听机制

SpringBoot在项目启动时,会对几个监听器进行回调,我们可以实现这些监听器接口,在项目启动时完成一些操作

image-20210911170816982

ApplicationContextInitializer

  • 位于contextPrepared()上下文准备之前,打印SpringBoot的启动banner之后调用此initialize()方法

  • 需要手动配置META-INF下的spring.factories

    1
    2
    org.springframework.context.ApplicationContextInitializer=\
    com.taoyyz.springbootlistener.listener.MyApplicationContextInitializer

SpringApplicationRunListener

  • 贯穿整个SpringBoot启动过程,需要一个带参构造方法

    1
    2
    3
    4
    5
    6
    public class MySpringApplicationRunListener implements SpringApplicationRunListener {
    public MySpringApplicationRunListener(SpringApplication application, String[] args) {
    System.out.println("application = " + application);
    System.out.println(Arrays.toString(args));
    }
    }
  • 需要手动配置META-INF下的spring.factories

    1
    2
    org.springframework.boot.SpringApplicationRunListener=\
    com.taoyyz.springbootlistener.listener.MySpringApplicationRunListener

CommandLineRunner

  • 项目启动结束后执行run()方法

ApplicationRunner

  • 项目启动结束后执行run()方法

SpringBoot启动流程分析

SpringBoot启动流程

  • @EnableAutoConfiguration利用了@Import(AutoConfigurationImportSelector.class)

  • AutoConfigurationImportSelector类实现了ImportSelector接口的selectImports(AnnotationMetadata annotationMetadata)方法,此方法中调用getAutoConfigurationEntry(annotationMetadata)返回一个AutoConfigurationEntry对象(此对象含有一个List<String>类型的configurations和一个Set<String>类型的 exclusions

  • 在上述的getAutoConfigurationEntry方法中调用了getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes)方法,来获取候选配置,此方法内部利用SpringFactoriesLoaderloadFactoryNames(Class<?> factoryType, ClassLoader classLoader)方法中调用loadSpringFactories(ClassLoader classLoader)方法去读取SpringFactoriesLoader中一个静态常量:public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"

  • loadSpringFactories(ClassLoader classLoader)方法中加载所有spring.factories文件

    image-20210913024040772

  • 然后loadFactoryNames(Class<?> factoryType, ClassLoader classLoader)方法会把上一步方法的返回值(一个多值Map<String, List<String>>)调用getOrDefault(factoryTypeName, Collections.emptyList()),这里传入的factoryTypeName其实就是EnableAutoConfiguration的全类名,也就是筛选出EnableAutoConfiguration注解所自动配置的key对应的value(这里value为List<String>),此时getCandidateConfigurations(annotationMetadata, attributes)方法结束。

  • 然后getAutoConfigurationEntry方法的下一步调用removeDuplicates(configurations)移除重复项目,这里利用了HashSet去重,根据HashSet内容构造一个ArrayList并返回

  • 下一步调用getExclusions(annotationMetadata, attributes)返回一个排除的Set<String>

  • 下一步调用checkExcludedClasses(configurations, exclusions)检查需要排除的Class是否为无效Class,检查无效首先在ClassUtils.isPresent()方法中利用forName()尝试加载这个exclusions的className,如果发生异常立即报错并返回false,其次即使forName()可以成功加载,还需要满足!configurations.contains(exclusion),也就是自动配置列表不包含exclusion这个需要排除的项时取反为true就会把此exclusion加入到invalidExcludes这个List<String>中去统一报错处理

  • 如果没有报错,说明上一步成功排除,下一步就调用configurations.removeAll(exclusions)移除正确的排除项

  • 下一步让所有配置在META-INF/spring.factories下的AutoConfigurationImportListener执行AutoConfigurationImportEvent事件

  • 最终getAutoConfigurationEntry(annotationMetadata)执行完毕,返回AutoConfigurationEntry中的configurationsString[]形式

SpringBoot监控

SpringBoot自带监控功能Actuator,可以实现对程序内部运行情况监控,例如监控状况、Bean加载情况、配置属性、日志信息等

  • 使用步骤:

    1. 导入依赖坐标

      1
      2
      3
      4
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
      </dependency>
    2. 访问http://localhost:8080/actuator

  • 配置监控:

    1
    2
    3
    4
    5
    6
    # 开启info监控
    management.endpoint.info.enabled=true
    # 暴露所有端口
    management.endpoints.web.exposure.include=*
    # 开启健康监控完整信息
    management.endpoint.health.show-details=always
  • web图形化监控:admin依赖

    1. 创建server端项目,引入server端依赖:(可能需要依赖管理中指定版本)

      1
      2
      3
      4
      <dependency>
      <groupId>de.codecentric</groupId>
      <artifactId>spring-boot-admin-starter-server</artifactId>
      </dependency>
    2. 在SpringBoot配置文件中配置server端访问端口:

      1
      server.port=9000
    3. 开启server端注解,此时可以启用server端项目了

      1
      2
      3
      4
      5
      6
      7
      @EnableAdminServer
      @SpringBootApplication
      public class SpringBootAdminServerApplication {
      public static void main(String[] args) {
      SpringApplication.run(SpringBootAdminServerApplication.class, args);
      }
      }
    4. 在client端中引入client端依赖:

      1
      2
      3
      4
      <dependency>
      <groupId>de.codecentric</groupId>
      <artifactId>spring-boot-admin-starter-client</artifactId>
      </dependency>
    5. 在client端的SpringBoot配置文件中配置client端的url(可根据需要暴露监控端口等)

      1
      2
      # 开启admin.server图形化监控
      spring.boot.admin.client.url=http://localhost:9000
    6. 访问http://localhost:9000即可进入图形化监控

SpringBoot异常处理

  • 使用全局异常处理器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //@RestControllerAdvice
    @ControllerAdvice
    public class MyExceptionHandler {
    //拦截异常
    @ExceptionHandler(Exception.class)
    public JsonResponse exceptionHandler(Exception e){
    //处理异常逻辑,可以是记录日志等操作
    e.printStackTrace();
    return JsonResponse.failure("服务器异常,原因是" + e.getMessage());
    }
    }

SpringBoot项目部署

启动SpringBoot项目

通过jar包的内置tomcat启动

  • SpringBoot的Maven项目默认打jar包,其中内置了tomcat服务器可以直接运行
    • 利用mvn package生成jar包
    • 利用java -jar ./项目jar包即可利用SpringBoot内置tomcat容器运行

通过war包在外置tomcat启动

  • 修改Maven的pom.xml,设置<packaging>war</packaging>打包类型为war

  • 修改@SpringBootApplication注解修饰的启动类,使其继承于SpringBootServletInitializer

  • 重写启动类中的configure()方法,传入启动类的class字节码对象

    1
    2
    3
    4
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
    return builder.sources(SpringBootDeployApplication.class);
    }
  • 利用mvn package生成war包

  • 复制war包到tomcat安装目录的webapps目录下,然后启动外置tomcat服务器

    访问路径含有上下文路径,也就是此项目在webapps目录下的文件夹名称

热部署SpringBoot项目

方式1:重新编译rebuild项目:ctrl+F9

方式2:通过插件自动构建部署

  1. 引入devtools依赖

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    </dependency>
  2. 开启IDEA自动构建

    image-20210912175546162
  3. 允许在运行时自动构建

    • ctrl+shift+alt+/维护窗口选择注册表选项: image-20210912175743818
    • 勾选在运行期编译image-20210912175848428
  4. 刷新页面访问会rebuild项目并热部署