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

个人博客:http://blog.kunpw.cn/

SpringBoot

  • 官方文档:https://docs.spring.io/spring-boot/docs/2.1.6.RELEASE/reference/htmlsingle/#boot-features-external-config

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是用于编写后续单元测试的依赖包:
    
    <dependencies>
            
        	
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
        
        	<dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-testartifactId>
                <scope>testscope>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintagegroupId>
                        <artifactId>junit-vintage-engineartifactId>
                    exclusion>
                exclusions>
            dependency>
    dependencies>
    
    • build:构件配置部分,默认使用了spring-boot-maven-plugin,即打jar包插件;

2.2 application.properties

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

2.3 banner.txt(小彩蛋)

  • 自建文件,重写启动器banner服务,src/main/javaresource/banner.txt目录文件;
  • 网站:https://www.bootschool.net/ascii
  • 例:

//                          _ooOoo_                               //
//                         o8888888o                              //
//                         88" . "88                              //
//                         (| ^_^ |)                              //
//                         O\  =  /O                              //
//                      ____/`---'\____                           //
//                    .'  \\|     |//  `.                         //
//                   /  \\|||  :  |||//  \                        //
//                  /  _||||| -:- |||||-  \                       //
//                  |   | \\\  -  /// |   |                       //
//                  | \_|  ''\---/''  |   |                       //
//                  \  .-\__  `-`  ___/-. /                       //
//                ___`. .'  /--.--\  `. . ___                     //
//              ."" '<  `.___\_<|>_/___.'  >'"".                  //
//            | | :  `- \`.;`\ _ /`;.`/ - ` : | |                 //
//            \  \ `-.   \_ __\ /__ _/   .-` /  /                 //
//      ========`-.____`-.___\_____/___.-`____.-'========         //
//                           `=---='                              //
//      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^        //
//            佛祖保佑       永不宕机     永无BUG                    //

2.4 XXXApplication.java

  • src/main/java/com/kun/XXXApplication.java目录文件;
//标注这是一个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文件:
//使用Controller注解标注说明并被加载
@Controller
//使用Mapping类注解来映射请求,即请求/hello时响应回该类
@RequestMapping("/hello")
public class HelloController {
    //在/hello地址请求之下再请求地址/hello即可获得下列响应
    @GetMapping("/hello")
    //当请求/hello/hello时,响应主体部分为hello()方法
    @ResponseBody
    public String hello(){
        return "hello";
    }
}
  • 注解和反射机制请参考博客:[https://kunpw.cn/2020/10/23/java/%E6%B3%A8%E8%A7%A3%E5%92%8C%E5%8F%8D%E5%B0%84/](https://kunpw.cn/2020/10/23/java/%E6%B3%A8%E8%A7%A3%E5%92%8C%E5%8F%8D%E5%B0%84/)

3.yaml(yml)语法

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

3.1 常用语法:

# 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类
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 +
                '}';
    }
}
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.0
    public class Dog {
        @Value("zhaoxiaoan")
        private String name;
        @Value("3")
        private Integer age;
    }
    
    • yaml文件赋值:
    # 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
    
    @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.0
@SpringBootTest
class DemoApplicationTests {

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

//输出为:
Dog{name=‘旺财’, age=3}


```java
@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比较图示:

SpringBoot(一)——自动装配原理_第1张图片

  • 功能:即yaml可以直接映射到对象而properties需要使用@Value一个一个赋值;

  • 松散绑定:即yaml中的last-name和类中的lastName是一样的,-后接的字母转为大写;

  • JSR303数据校验:可以在字段增加一层过滤验证,保证数据合法性;

  • 复杂类型封装:yml中可以封装对象,使用@Value就不支持;

  • JSR303数据校验(类似于html中input标签中选中的url、email等功能,可以自动校验输入):

    @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依赖,所以需要手动添加依赖才能生效:

      <dependency>
            <groupId>org.hibernate.validatorgroupId>
            <artifactId>hibernate-validatorartifactId>
            <version>6.0.17.Finalversion>
            <scope>compilescope>
       dependency>
      
    • 再次测试则会得到报错结果:

    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校验注解类:

    SpringBoot(一)——自动装配原理_第2张图片

    SpringBoot(一)——自动装配原理_第3张图片

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

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格式中则实现多文档模块,步骤更为简便:

      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:自动装配,核心包:
    >@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中

SpringBoot(一)——自动装配原理_第4张图片

  • 文件内容:
    SpringBoot(一)——自动装配原理_第5张图片

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

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

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

5.2 yaml文件与自动装配

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

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

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

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

5.3 SpringApplication.run启动

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

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

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

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

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

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

5.3 SpringApplication.run启动

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

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

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

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

你可能感兴趣的:(狂神java学习)