朝小闇的博客

海上月是天上月,眼前人是心上人

SpringBoot(一)——自动装配原理

SpringBoot

1.创建SpringBoot文件

1.1 官网创建文件并下载,使用IDEA打开

1.2 IDEA创建

  • New Project -> Spring Initializr(本质也是从官网下载的模板格式);
  • 添加 java web 依赖;

2.文件简析

2.1 pom.xml

  • 配置文件,主要有四个部分:

    • 项目元数据信息:创建时输入的Project Metadata部分,也就是Meaven项目的基本元素,包括groupId、artifactId、version、name、description等;
    • parent:继承spring-boot-starter-parent的依赖管理、控制版本和打包等内容;
    • dependencies:项目具体依赖,下面包含了spring-boot-starter-web用于实现HTTP接口(该依赖包含了Spring MVC,且使用Tomcat作为默认嵌入式容器),spring-boot-starter-test是用于编写后续单元测试的依赖包:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <!--  在这个标签中添加  -->
    <dependencies>
    <!-- 添加依赖,格式为 spring-boot-starter- 这是springboot启动器的标准格式 -->
    <!-- web依赖,tomcat -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    <exclusions>
    <exclusion>
    <groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
    </exclusion>
    </exclusions>
    </dependency>
    </dependencies>
    • build:构件配置部分,默认使用了spring-boot-maven-plugin,即打jar包插件;

2.2 application.properties

  • src/main/javaresource/application.properties目录核心配置文件;
  • 重写服务属性,稍后详述;
  • 编辑对象、定义属性;
  • 常用小服务:
1
2
# 直接更改项目端口号即可,修改之后再次访问该端口才能成功
server.port=8081

2.3 banner.txt(小彩蛋)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
////////////////////////////////////////////////////////////////////
// _ooOoo_ //
// o8888888o //
// 88" . "88 //
// (| ^_^ |) //
// O\ = /O //
// ____/`---'\____ //
// .' \\| |// `. //
// / \\||| : |||// \ //
// / _||||| -:- |||||- \ //
// | | \\\ - /// | | //
// | \_| ''\---/'' | | //
// \ .-\__ `-` ___/-. / //
// ___`. .' /--.--\ `. . ___ //
// ."" '< `.___\_<|>_/___.' >'"". //
// | | : `- \`.;`\ _ /`;.`/ - ` : | | //
// \ \ `-. \_ __\ /__ _/ .-` / / //
// ========`-.____`-.___\_____/___.-`____.-'======== //
// `=---=' //
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //
// 佛祖保佑 永不宕机 永无BUG //
////////////////////////////////////////////////////////////////////

2.4 XXXApplication.java

  • src/main/java/com/kun/XXXApplication.java目录文件;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//标注这是一个SpringBoot应用,加载所有资源类
@SpringBootApplication
public class Demo01Application {
//程序入口
//run,开启了一个服务,参数一:应用入口的类,参数二:命令行输入的参数类
/*SpringApplication实例化,做了以下四项工作:
1.判断该应用类型是普通java项目还是Web项目
2.查找并加载所有可用初始化器,设置到initializers属性中
3.找出所有的应用程序监听器,设置到listeners属性中
4.推断并设置main方法的定义类,找到运行的主类
* */
public static void main(String[] args) {
SpringApplication.run(Demo01Application.class, args);
}

}

2.5 XXXApplicationTests.java

  • test/java/com/kun/XXXApplicationTests.java目录文件;
  • 实现单元测试功能;

2.6 自建框架层

  • src/main/java/com/kun/最底层包目录下新建configcontrollerdaopojo等包,这些具体用到再详细讲解,也可以参考博客简略了解一下:https://www.cnblogs.com/tooyi/p/13340374.html
  • 为说明几个常用注解以及习惯注解的用法,新建controller包,并在该目录下新建HelloController.java文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
//使用Controller注解标注说明并被加载
@Controller
//使用Mapping类注解来映射请求,即请求/hello时响应回该类
@RequestMapping("/hello")
public class HelloController {
//在/hello地址请求之下再请求地址/hello即可获得下列响应
@GetMapping("/hello")
//当请求/hello/hello时,响应主体部分为hello()方法
@ResponseBody
public String hello(){
return "hello";
}
}

3.yaml(yml)语法

  • yaml文件是对properties文件的替代,但比properties更实用,所以一般使用yaml;
  • 删除application.properties文件,新建application.yaml(.yml);

3.1 常用语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 1.基础语法
# 修改自动配置的默认值,其中 : 后必须接空格
# yaml 对空格很敏感,很像python 而properties只能保存键值对

server:
port: 8081

# yaml基本语法
# 定义参数使用key-value
name: zhaoxiaoan

# 定义对象
student:
name: zhaoxiaoan
age: 3

# 对象的行内写法
teacher: {name: zhao,age 30}

# 定义数组
pets:
- cat
- dog
- pig

# 数组的行内写法
pet: [cat,dog,pig]

# EL表达式
age: ${random.int}

# 占位符
person:
hello: happy
dog:
name: ${person.hello:hello}_旺财
# 如果没有就直接为空,有则占位显示,即happy_旺财

3.2 重点:给实体类赋值:

  • 新建数据类:新建pojo/Dog.java、pojo/Person.java类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.kun.pojo;

import org.springframework.stereotype.Component;

@Component
public class Dog {
private String name;
private Integer age;

//有参无参构造器、get/set方法以及toString方法,后续这些方法不再细列
public Dog() {
}

public Dog(String name, Integer age) {
this.name = name;
this.age = age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.kun.pojo;

import java.util.Date;
import java.util.List;
import java.util.Map;

public class Person {
private String name;
private Integer age;
private Boolean happy;
private Date birth;
private Map<String,Object> maps;
private List<Object> lists;
private Dog dog;

//省略各方法
}

  • 赋值:

    • 直接new对象;
  • 原生注解赋值:

    1
    2
    3
    4
    5
    6
    7
    //1.0
    public class Dog {
    @Value("zhaoxiaoan")
    private String name;
    @Value("3")
    private Integer age;
    }
    • yaml文件赋值:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      # 2.0
      person:
      name: zhaoxiaoan
      age: 3
      happy: true
      birth: 2020/10/22
      maps: {k1: v1,k2: v2}
      lists:
      - code
      - music
      - study
      dog:
      name: 旺财
      age: 3
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      @Component
      /*@PropertySource(value = "classpath:application.yaml")
      通过该注解设置装配文件
      */
      //通过ConfigurationProperties(prefix = "person")注解绑定映射yaml文件中person对象
      @ConfigurationProperties(prefix = "person")
      public class Person {
      private String name;
      private Integer age;
      private Boolean happy;
      private Date birth;
      private Map<String,Object> maps;
      private List<Object> lists;
      private Dog dog;
      }
  • 单元测试:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //1.0
    @SpringBootTest
    class DemoApplicationTests {

    //自动装配类
    //如果有多个对象Dog,可以通过@Qualifier选择具体的对象
    @Autowired
    private Dog dog;
    @Test
    void contextLoads() {
    System.out.println(dog);
    }
    }

    //输出为:
    Dog{name='旺财', age=3}
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @SpringBootTest
    class DemoApplicationTests {

    @Autowired
    private Person person;
    @Test
    void contextLoads() {
    System.out.println(person);
    }
    }

    //输出为:
    Person{name='zhaoxiaoan', age=3, happy=true, birth=Thu Oct 22 00:00:00 CST 2020, maps={k1=v1, k2=v2}, lists=[code, music, study], dog=Dog{name='旺财', age=3}}

3.3 yaml-ConfigurationProperties与原properties-value比较图示:

image-20201022231250885

  • 功能:即yaml可以直接映射到对象而properties需要使用@Value一个一个赋值;
  • 松散绑定:即yaml中的last-name和类中的lastName是一样的,-后接的字母转为大写;
  • JSR303数据校验:可以在字段增加一层过滤验证,保证数据合法性;
  • 复杂类型封装:yml中可以封装对象,使用@Value就不支持;
  • JSR303数据校验(类似于html中input标签中选中的url、email等功能,可以自动校验输入):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Component
    //@PropertySource(value = "classpath:application.yaml")
    @ConfigurationProperties(prefix = "person")
    @Validated//开启数据校验
    public class Person {
    @Email//对email赋值开启Email数据校验,假设对name属性开启Email验证
    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;
    }
    • 注意,在SpringBoot2.3以后版本由于spring-boot-starter-web的依赖项已经去除了validate依赖,所以需要手动添加依赖才能生效:

      1
      2
      3
      4
      5
      6
      <dependency>
      <groupId>org.hibernate.validator</groupId>
      <artifactId>hibernate-validator</artifactId>
      <version>6.0.17.Final</version>
      <scope>compile</scope>
      </dependency>
    • 再次测试则会得到报错结果:

    1
    2
    3
    4
    5
    6
    7
    8
    Description:

    Binding to target org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'person' to com.kun.pojo.Person failed:

    Property: person.name
    Value: zhaoxiaoan
    Origin: class path resource [application.yaml]:2:9
    Reason: 不是一个合法的电子邮件地址
    • 修改name属性赋值即可;
    • JSR303校验注解类:

    img

    img

    • 小技巧:可以进入注解原文件查看各种注解值并自主修改;

4.yaml配置文件位置和多环境配置

4.1 配置文件

  1. file:./config/
  2. file:./
  3. classpath:/config/
  4. classpath:/
  • file即项目目录,classpath即src/main/resources目录;

  • 这既是yaml配置文件可存放的位置,也是其配置文件加载的优先级权重顺序,即按照权重覆盖相同属性及服务配置;

4.2 多环境配置

  • 现实开发中可能会有三种环境版本:

    • 普通环境:application.properties配置文件;
    • 测试环境:application-test.properties配置文件;
    • 开发环境:application-dev.properties配置文件;
  • yaml格式和properties格式配置环境对比:

    • 在properties文件格式下徐亚单独配置多个文件才能生效;

    • 而在yaml格式中则实现多文档模块,步骤更为简便:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      server:
      port: 8081

      # 通过`---`符号分割不同配置环境,并使用spring.profiles属性说明即可
      ---
      server:
      port: 8082
      spring:
      profiles: dev

      ---
      server:
      port: 8083
      spring:
      profiles: test

5.自动装配原理(重点)

@SpringBootApplication

5.1 原理

  • src/main/java/com/kun/XXXApplication.java程序入口文件@SpringBootApplication注解出发,依次查看父级文件,了解其自动装配原理:

    • @ComponentScan:其父级文件注解,用来实现扫描当前主启动类同级的包;
    • @SpringBootConfiguration——>@Configuration——>@Component:分别实现springboot配置、spring配置和基本组件;
    • @EnableAutoConfiguration:自动装配,核心包:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    —>@AutoConfigurationPackage:自动配置包
    ->@Import({Registrar.class}):自动配置包注册
    ->@Import({AutoConfigurationImportSelector.class}):自动配置导入选择器
    ->getAutoConfigurationEntry():获得自动配置的实体入口
    ->getCandidateConfigurations():获取候选配置
    ->protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    return EnableAutoConfiguration.class; //标注了EnableAutoConfiguration注解类的所有配置
    }
    ->loadFactoryNames():获取所有的加载配置名字
    ->loadSpringFactories():加载所有的Spring工厂
    ->classLoader.getResources("META-INF/spring.factories"):加载项目资源
    ->ClassLoader.getSystemResources("META-INF/spring.factories"):加载系统资源
    ->META-INF/spring.factories:从该文件获取资源,自动装配的核心文件
  • META-INF/spring.factories文件:存在于External Libraries中

image-20201023160248610

  • 文件内容:
    image-20201023160458985

    • 所有已经配置好的依赖都在这里,其它只能自己配置;

    • 随便进入一个源文件(可能只是.class文件,根据提示下载源java文件查看):

      1
      @ConditionalOnXXX //系列核心注解,其中每个文件都有,用来判断是否满足一系列条件才加载该文件
  • 结论一:SpringBoot所有自动装配都是在启动的时候扫描并加载的,其中所有的自动装配类都在spring.factories文件中,只有在pom.xml文件中添加了依赖才满足生效条件并且自动装配生效;

5.2 yaml文件与自动装配

  • yaml文件内能够重写的服务实质上和spring.factories文件相关:

    1
    @EnableConfigurationProperties //某些自动装配文件中含有此注解,即标注可以在properties配置文件中重写属性值
    • 在构造器类中:
    1
    2
    3
    4
    5
    //举例如下:
    public HttpEncodingAutoConfiguration(ServerProperties properties) {
    this.properties = properties.getServlet().getEncoding();
    }
    //其中ServerProperties文件中的属性即与yaml配置文件绑定,可以重写配置
    • ServerProperties:
    1
    2
    @ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
    //该注解中prefix值表示yaml文件中重写配置该类属性的前缀
    • 同样可以从yaml文件中根据配置属性返回到上级XXXProperties文件查看所有同类属性;
  • 结论二:springfactories中定义好的自动装配类XXXAutoConfiguration加载XXXProperties文件,而XXXProperties文件则与配置文件.properties/.yaml一一绑定,即可通过.yaml配置文件修改配置并被加载;

  • 开启Debug,查看具体哪些配置类被加载:

    1
    2
    # 在application.yaml文件中开启Spring调试类
    debug: true
    • 测试结果:
    1
    2
    3
    Positive matches: 已经启用生效的类
    Negative matches: 没有开启生效的类
    Unconditional classes: 没有条件的类

5.3 SpringApplication.run启动

  • src/main/java/com/kun/XXXApplication.java文件中:

    1
    SpringApplication.run(DemoApplication.class, args);
    • 该方法主要分两部分:

      • SpringApplication实例化;
      • run方法执行;
    • 做了四项工作:

      • 判断该应用类型是普通java项目还是Web项目;
      • 查找并加载所有可用初始化器,设置到initializers属性中;
      • 找出所有的应用程序监听器,设置到listeners属性中;
      • 推断并设置main方法的定义类,找到运行的主类;
-------- 本文结束 感谢阅读 --------