Java:114-Spring Boot的底层原理(上篇)

Spring Boot的底层原理

之前学习过了Spring Boot(在88章博客),但是并没有很深入的了解,这里致力于在原来的基础上学习更多Spring Boot知识
回顾(注意:只是回顾,所以更多细节在88章博客):
约定优于配置:
Spring Boot 是所有基于 Spring 开发的项目的起点,Spring Boot 的设计是为了让你尽可能快的跑起来,Spring 应用程序并且尽可能减少你的配置文件
约定优于配置(Convention over Configuration),又称按约定编程,是一种软件设计范式
本质上是说,系统、类库或框架应该假定合理的默认值,而非要求提供不必要的配置,比如说模型中有一个名为User的类,那么数据库中对应的表就应该默认命名为user,只有在偏离这一个约定的时候,例如想要将该表命名为person,才需要写有关这个名字的配置
比如平时架构师搭建项目就是限制软件开发随便写代码,制定出一套规范,让开发人员按统一的要求进行开发编码测试之类的,这样就加强了开发效率与审查代码效率,所以说写代码的时候就需要按要求命名,这样统一规范的代码就有良好的可读性与维护性了
约定优于配置简单来理解,就是遵循约定,比如,现在有一个依赖,我Spring需要他,那么Spring定义一部分与他连接,形成一个新的依赖,让这个依赖使用,这个时候Spring可以直接使用他,再考虑自动的配置类等等,于是乎,由于这样的约定越来越多,就形成了对应的起始依赖(通用的,或者某些考虑自动的配置,这里再后面会说明,通常在自动配置的原理那里,一般是一开始的父依赖),并且再后续进行维护,且许多框架都来进行整合,这样的整体,就是Spring Boot了,所以说Spring Boot不只是一个思想(约定),也是具体实现(依赖),只不过是各个框架整体的实现
SpringBoot概念:
Spring优缺点:
优点: spring是Java企业版(Java Enterprise Edition,JEE,也称J2EE)的轻量级代替品,无需开发重量级的 Enterprise JavaBean(EJB),Spring为企业级Java开发提供了一种相对简单的方法,通过依赖注入和面向切面编程,用简单的Java对象(Plain Old Java Object,POJO)实现了EJB的功能
缺点: 虽然Spring的组件代码是轻量级的,但它的配置却是重量级的,一开始,Spring用XML配置,而且是很多XML配置,Spring 2.5引入了基于注解的组件扫描,这消除了大量针对应用程序自身组件的显式XML 配置,Spring 3.0引入 了基于Java的配置,这是一种类型安全的可重构配置方式,可以代替XML,所有这些配置都代表了开发时的损耗,因为在思考Spring特性配置和解决业务问题之间需要进行思维切换,所以编写配置挤占了编写应用程序逻辑的时间,和所有框架一样,Spring实用,但与此同时它要求的回报也不少,除此之外,项目的依赖管理也是一件耗时耗力的事情,在环境搭建时,需要分析要导入哪些库的坐标, 而且还需要分析导入与之有依赖关系的其他库的坐标,一旦选错了依赖的版本,随之而来的不兼容问题 就会严重阻碍项目的开发进度,还有,虽然在一定程度上简化了配置处理,但是其配置在一定量的代码下,比xml需要更多的性能,只不过大多数是忽略的,当然,这是建立在扫描很多包的情况下(前面博客很多情况下,说明的都是这个),通常来说xml由于需要操作IO,所以性能会更加的大,也就是说,平常注解的开销就是比xml要小,所以建议使用注解,当然了,为了更加的明确,这里就给出具体的情况吧,考虑任何情况下注解和xml的区别:
注解和xml的区别和优缺点:
注解:通过反射来完成的配置,在代码里面进行处理
xml:通过配置文件来完成的配置,在配置文件中进行处理
在读取方面的区别:一个是反射,一个是IO
在运行后方面的区别:在代码中不可修改,在配置文件中可以修改
在开发方便的区别:注解简单,xml编写困难
这样要考虑性能,就看反射开销和IO开销谁大,看维护性,就看是否需要动态的修改(前提是可以读取)
那么由于通常情况下IO的开销是比较大的,所以呢,在不考虑其他因素,那么注解的启动效率基本都高于xml(读取后就固定了,所以只会考虑启动),但是由于注解绝对的运行时不可改变,所以在后续维护中也必然小于xml
从上面讲,那么需要考虑如下的问题:
考虑启动的快慢:那么使用注解
考虑后续的维护:那么使用xml
考虑开发的便捷:那么使用注解
很明显,在现在的环境下,我们通常需要启动快,便捷的方式,所以注解的操作现在非常流行
SpringBoot解决上述spring的问题:
SpringBoot对上述Spring的缺点进行的改善和优化,基于约定优于配置的思想,可以让开发人员不必在配置与逻辑业务之间进行思维的切换,全身心的投入到逻辑业务的代码编写中,从而大大提高了开发的效率,一定程度上缩短 了项目周期
起步依赖 :
起步依赖本质上是一个Maven项目对象模型(Project Object Model,POM),定义了对其他库的传递依赖,这些东西加在一起即支持某项功能,简单的说,起步依赖就是将具备某种功能的坐标打包到一起,并提供一些默认的功能,通常用来解决依赖多的情况(一个依赖包含多个依赖)
自动配置:
springboot的自动配置,指的是springboot,会自动将一些配置类的bean注册进ioc容器(有些通常需要指定),我们可以需要的地方使用@autowired或者@resource等注解来使用它,"自动"的表现形式就是我们只需要引我们想用功能的包,相关的配置我们完全不用管,springboot会自 动注入这些配置bean,我们直接使用这些bean即可,所以springboot可以简单、快速、方便地搭建项目,对主流开发框架的无配置集成,极大提高了开发、部署效率(具体之所以可以配置是因为他依赖整合的),解决需要手动扫描的问题(虽然也是一个注解造成的全扫描(这个扫描是任何可以的框架的扫描,而不是单独的,所以解决了手动扫描的问题),但是我只需要一个注解即可)
现在看一下案例吧,创建项目(不会,到88章博客学习吧,也记得配置好maven哦):
对应的依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com</groupId>
    <artifactId>boot</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>boot</name>
    <description>boot</description>
    <properties>
        <java.version>11</java.version> <!--看好版本-->
    </properties>
    <dependencies>
        <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>
        </dependency>
    </dependencies>

    <!--
这里进行打包时,可以考虑为jar包,jar包是类似于war包中的运行方式
jar包与war包的区别:
启动:
jar包是操作main启动所形成的处理
war包则是根据web容器(如tomcat)来启动的,但是本质上也是main来处理的
所以jar和war都是main来处理,只不过处理方式不同,jar是自身main,而war是容器main
是否具有前端页面:
jar包也存在,通常指使用WebJars来完成类似于web容器中对页面的访问,集成了main与页面的联系
war则是直接考虑web中与页面的联系,也就是使用tomcat来联系
所以jar使用WebJars来联系,而war使用tomcat来联系

虽然上面说WebJars,但是实际上他只是一个补充,真实情况下,jar包一般是war和tomcat的集合体,所以启动jar相当于启动了tomcat和war,也就不用配置服务器了(即,spring boot通常内嵌tomcat,前提是有对应的mvc依赖(其整合的spring-boot-starter-web))

-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

<!--
    所有的springboot项目,都会直接或者间接的继承spring-boot-starter-parent
    作用:对项目依赖的版本进行管理,当前项目再引入其他常用的依赖时就不需要再指定版本号,避免版本冲突的问题,存在默认的资源过滤和插件管理


    -->
对应的项目:

在这里插入图片描述

在boot包下,创建controller包,然后创建HelloController类:
package com.boot.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("hello")
public class HelloController {
   


    @RequestMapping("/boot")
    public String helloBoot() {
   
        return "Hello Spring Boot";
    }

}
启动类:
package com.boot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class BootApplication {
   

        //你只是定义注解并可以操作找到启动类的路径,但是springboot并没有真正的执行
        //也就是说,该注解并没有起作用
        //需要使用这个方法才可,并传入启动类的字节码文件,及其对应main方法的参数
        //执行后springboot会找到帮助@SpringBootApplication注解
        //并根据该注解进行扫描他的包及其子包
        //那么springboot就会使得帮我们准备所有的环境,包括server,监听器,装配spring的上下文等等
    public static void main(String[] args) {
   
        SpringApplication.run(BootApplication.class, args);
    }

}

/*
 SpringBoot的启动类,通常放在二级包中,比如这里的:com.boot下
(但必须在某个包里面,而不能直接的在资源文件的下层目录,否则启动报错,这是规定,底层的原因,为什么会这样,你可以试着将一个类放在java资源文件夹下,然后在一个包里,来导入该类,你会发现,不能导入,为什么:因为他没有包指定,那么就应该直接的import 类名,但是他的意思也包括在当前包下导入,所以冲突了,一般来说,以当前包为主,所以你是导入不了该类的,即这里虽然说是规定,但又何尝不是因为导入不了包而形成的错误呢,可能某些操作会导入该包吧,比如,通过注解得到包名称,创建一个文件,里面加上该导入,然后通过java操作命令行执行,因为java本身好像操作这样的是执行不了的,可以使用网上的某些工具类,当然这只是想象中而已,可能实际上是某些判断,导致的)
因为SpringBoot在做包扫描时,会扫描启动类所在的包,及其子包下的所有内容
实际情况:一般我们会在com.boot下创建对应的dao包,service包等等,如果启动类不在com.boot下
而在dao包里面,那么service也就扫描不到了
所以这时可能会出现问题(如扫描的注解不进行操作,使得对应的环境没有)


@SpringBootApplication该注解标识当前类为SpringBoot的启动类
这时,springboot扫描项目时(先进行扫描,后进行启动,这个扫描只找@SpringBootApplication该注解)
找到该注解,并可以得到对应的包路径
就会扫描其包及其子包下的其他内容(这个扫描是扫描spring注解的)

这个扫描一般在其父依赖中,也就是:
 <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    没有这个依赖,也就没有这个注解
*/


我们直接的启动上面的启动类,http://localhost:8080/hello/boot,上面的依赖版本和jdk版本是需要适配的,否则可能启动不了,还有,在104章博客中,还有关于一些项目版本的说明,可以看一看
如果访问后出现了数据,那么我们操作成功了
单元测试与热部署 :
单元测试 :
开发中,每当完成一个功能接口或业务方法的编写后,通常都会借助单元测试验证该功能是否正确,Spring Boot对项目的单元测试提供了很好的支持,在使用时,需要提前在项目的pom.xml文件中添加spring-boot-starter-test测试依赖启动器,快速构建springboot项目一般会加上该依赖,即使用Spring Initializr方式搭建的Spring Boot项目,会自动加入spring-boot-starter-test测试依赖启动器,无需再手动添加,然后可以通过相关注解实现单元测试
依赖:
 <dependency>
     <!--使得可以进行测试spring boot,这个@SpringBootTest需要这个依赖-->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
  <dependency>
      <!--一般需要他来操作测试,如@RunWith(JUnit4.class)-->
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
在测试类中BootApplicationTests中加上如下:
package com.boot;

import com.boot.controller.HelloController;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
class BootApplicationTests {
   

    @Autowired
    private HelloController helloController;

    @Test
    public void contextLoads() {
   
        String s = helloController.helloBoot();
        System.out.println(s); //Hello Spring Boot
    }

}

启动,若有结果说明操作成功
热部署:
在开发过程中,通常会对一段业务代码不断地修改测试,在修改之后往往需要重启服务,有些服务需要加载很久才能启动成功,这种不必要的重复操作极大的降低了程序开发效率,为此, Spring Boot框架专门提供了进行热部署的依赖启动器,用于进行项目热部署,而无需手动重启项目(也就是重新执行启动类,或者说重新执行启动类的main方法)
简单来说,热部署就是:在修改完代码之后(无论是配置文件还是类,基本只要是项目的进行了修改就会更新)
不需要重新启动容器,就可以实现更新,但是需要等待他更新
等日志出现,那么才会真正的部署,中途再次改变,会影响日志的出现
一般会迟一点,因为有缓冲等待(大概等几秒才更新),防止你频繁的更新(每次的改变,基本会重置该等待)
使用步骤:
1:添加SpringBoot的热部署依赖启动器
2:开启Idea的自动编译
3:开启Idea的在项目运行中自动编译的功能
添加spring-boot-devtools热部署依赖启动器:
在Spring Boot项目进行热部署测试之前,需要先在项目的pom.xml文件中添加spring-boot-devtools热部署依赖启动器:
    <!--引入热部署依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
由于使用的是IDEA开发工具,添加热部署依赖后可能没有任何效果,接下来还需要针对IDEA开发工具进行热部署相关的功能设置
IDEA工具热部署设置:
选择IDEA工具界面的【File】 ->【Settings】选项,打开Compiler面板设置页面

在这里插入图片描述

在这里插入图片描述

然后启动项目(不是测试的),访问:http://localhost:8080/hello/boot,然后修改打印信息,直接看看结果吧
全局配置文件 :
全局配置文件能够对一些默认配置值进行修改
Spring Boot使用一个application.properties或者application.yaml的文件作为全局配置文件
该文件存放在src/main/resource目录或者类路径的/config,一般会选择resource目录
接下来,将针对这两种全局配置文件进行讲解 :
Spring Boot配置文件的命名及其格式:
application.properties,application.yaml,application.yml(前面一个的简写,相当于是一样的)
application.properties配置文件 :
使用Spring Initializr方式构建Spring Boot项目时,会在resource目录下自动生成一个空的application.properties文件(通常是空的)
Spring Boot项目启动时会自动加载application.properties文件
我们可以在application.properties文件中定义Spring Boot项目的相关属性
当然,这些相关属性可以是系统属性、环境变量、命令参数等等信息,也可以是自定义配置文件名称和位置
比如:
#修改tomcat的端口号(有些时候也可以说是版本号)
server.port=8888
现在我们重新启动,就需要访问http://localhost:8888/hello/boot了
还需要注意一点:IDEA 通常会自动保存管理的项目的文件的(是管理的项目哦,通常指该文件夹下的所有,也可以认为是.idea所在文件夹下面的所有)
比如:
1:窗口切换:当你切换到另一个应用程序或窗口时,IDEA 会自动保存当前的更改
2:运行/调试:当你运行或调试项目时,IDEA 会自动保存所有未保存的文件
3:版本控制操作:在执行与版本控制相关的操作(如提交、推送、拉取)时,IDEA 会自动保存所有更改
4:特定时间间隔:如果启用了相应的设置,IDEA 会在一段时间的空闲之后自动保存文件(通常来说是默认启动的)
这些都会使得保存文件,所以在idea中不用担心文件没有保存哦,如果不放心可以ctrl+s保存一下的
继续说明全局配置文件,我们可以给全局配置文件加上如下:
#注意:
#spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver中
#他后面不能有空格,否则报错,这是一个特殊的地方,当然可以不写,会根据驱动默认加上的(这一般是spring boot自身的处理,其他的如单纯的spring可能还是需要指定)
#其他的基本都可以有空格(且空格无影响,相当于没有什么,基本不会参与到数值里面去)
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/ceshi
spring.datasource.username=root
spring.datasource.password=123456
操作了对应的数据库,那么一般需要对应的驱动包
spring boot的依赖中并不是所有的包都有传递,有些需要自己加上,如这个驱动包:
 <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>6.0.6</version> 
     <!--
如果自己写版本,则使用自己的,但是因为spring boot的版本一般是经过测试的,也就基本没有版本冲突
自己写的版本,可能会出现冲突,所以也最好不要自己写(除非你确定不会发生冲突)
-->
        </dependency>
还需要如下的包:
  <dependency>
      <!--通常spring boot加上就会在内部使用数据库,所以当没有配置数据库时,他可能会使得启动报错-->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
            <version>2.7.1</version>
      <!--
虽然spring boot一般是进行测试的,但也有可能对应的包突然不存在(不开放了),所以这时需要指定版本
虽然基本不会,但这里使用我写的版本,实际上使用默认的也可
-->
        </dependency>
<!--一般有对应的spring-jdbc包,使用连接池的,可以被注入-->
至此我们可以操作如下:
package com.boot.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("hello")
public class HelloController {
   


    @RequestMapping("/boot")
    public String helloBoot() {
   
        return "Hello Spring Boot";
    }

    @Autowired
    private JdbcTemplate jdbcTemplate; //可能会报红,但这是idea检查的操作,运行时不会出错的

    @RequestMapping("/jdbc")
    public String jdbc() {
   
        return jdbcTemplate.toString(); //只是打印,所以前面的数据库没有也没有关系,因为并没有去操作

    }
}
访问一下吧,http://localhost:8888/hello/jdbc
为了进一步的说明配置文件,我们在boot包下创建pojo包,然后创建如下的类:
package com.boot.pojo;

public class Pet {
   
    private String type; //品种
    private String name; //名称

    @Override
    public String toString() {
   
        return "Pet{" +
                "type='" + type + '\'' +
                ", name='" + name + '\'' +
                '}';
    }

    public String getType() {
   
        return type;
    }

    public void setType(String type) {
   
        this.type = type;
    }

    public String getName() {
   
        return name;
    }

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

package com.boot.pojo;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

@Component //记得要被spring boot扫描到
//将配置文件中所有以person开头的配置信息注入到当前类中
//前提1:必须保证配置文件中person.xxx的xxx要与Person类的setxxx中的xxx一致(首字母可以忽略大小写)
//前提2:必须保证当前Person中的属性都具有set方法,因为是使用setxxx方法进行注入的
//若没有,如没有满足对应的存在(首字母可以忽略大小写,则代表没有,那么会报错)
@ConfigurationProperties(prefix = "person")
public class Person {
   

    private int id; //id
    private String name; //名称
    private List hobby; //爱好
    private String[] family; //家庭成员
    private Map map;
    private Pet pet; //宠物

    /*
    当然有类似于这样的如下:
    @Value("${person.id}")
    private int id; //id
    @Value("${person.name}")
    private String name; //名称
    @Value("${person.hobby}")
    private List hobby; //爱好
    @Value("${person.family}")
    private String[] family; //家庭成员
    private Map map;
    private Pet pet; //宠物
    但是一般却不能直接操作对应的map集合和类,因为这里只给出了参数(因为参数只能是一个)
    具体的解决方案可以百度,一般来说会使得配置文件里面参数进行包括起来,只包含一个参数
    */


    @Override
    public String toString() {
   
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", hobby=" + hobby +
                ", family=" + Arrays.toString(family) +
                ", map=" + map +
                ", pet=" + pet +
                '}';
    }

    public int getId() {
   
        return id;
    }

    public void setId(int id) {
   
        this.id = id;
    }

    public String getName() {
   
        return name;
    }

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

    public List getHobby() {
   
        return hobby;
    }

    public void setHobby(List hobby) {
   
        this.hobby = hobby;
    }

    public String[] getFamily() {
   
        return family;
    }

    public void setFamily(String[] family) {
   
        this.family = family;
    }

    public Map getMap() {
   
        return map;
    }

    public void setMap(Map map) {
   
        this.map = map;
    }

    public Pet getPet() {
   
        return pet;
    }

    public void setPet(Pet pet) {
   
        this.pet = pet;
    }
}
@ConfigurationProperties(prefix = “person”)注解的作用:
将配置文件中以person开头的属性值通过setXX()方法注入到实体类对应属性中
@Component注解的作用是将当前注入属性值的Person类对象作为Bean组件放到Spring容器中
只有这样才能被@ConfigurationProperties注解进行赋值,就如@Value一样需要对应的实例
但是若@ConfigurationProperties注解和@Value共存,那么@ConfigurationProperties注解会覆盖@Value注解的操作
若@Value注解报错,那么启动基本就会报错,使得访问不了
打开项目的resources目录下的application.properties配置文件,在该配置文件中编写需要对Person类设置的配置属性
#自定义配置信息注入到Person对象中
person.id=100
person.name=哈哈
#list
person.hobby=喝水,吃早餐 
#String[]
person.family=儿子,老婆
#集合和对象,一般都需要多一个层(多了.)
#Map
person.map.k1=v1
person.map.k2=v2
#Pet对象
person.pet.type=狗
person.pet.name=旺财
#他们可以使用@Value注解来进行注入(对应的参数获取),因为他们是全局的
在测试类这加上如下:
 @Autowired
    private Person person;

    @Test
    public void configurationTest(){
   
        System.out.println(person);
        /*
        Person{id=100, name='哈哈', hobby=[喝水,吃早餐], family=[儿子,老婆], 
        map={k1=v1, k2=v2}, pet=Pet{type='狗', name='旺财'}}
        */
    }
若返回数据则代表注入成功,即操作成功
具体的编码问题可以参考88章博客
application.yaml(yml)配置文件:
YAML文件格式是Spring Boot支持的一种JSON文件格式,相较于传统的Properties配置文件, YAML文件以数据为核心,是一种更为直观且容易被电脑识别的数据序列化格式
application.yaml配置文件的工作原理和application.properties是一样的,只不过yaml格式配置文件看起来更简洁一些,他们的区别主要在于基本上springboot的配置他们都有联系,但是并非都有编写,所以大多数情况下,可以通过properties来得到yml的编写,但是有些只有yml才可以或者只有properties才可以(少部分),相当于整合的依赖中,都存在他们的配置,只不过有些只操作yml,有些只操作properties,现在我们基本考虑yml,所以后面也基本以这个为主的(yaml与yml是一样的,解析也是一样,当然,可能某些只会看后缀名称,但是这里非常少,所以这里不考虑)
YAML文件的扩展名可以使用.yml或者.yaml,application.yml文件使用 "key:(空格) value"格式配置属性,使用缩进控制层级关系
SpringBoot的三种配置文件是可以共存的,也就是说,可以写这三个配置文件,都会进行读取:
<!--
点击spring-boot-starter-parent(使用ctrl+鼠标左键),往下找可以找到
当然修改这个里面的值,不会操作当前项目,因为他不是项目里面的
他只是一个投影而已(可以试着全部删除,然后启动运行,发现并没有影响)
--> 
<includes>
    <!--谁在前面,谁基本先读取,当然他可能并不绝对,也受版本影响(他这里可能还是不变,但顺序不同)-->
          <include>**/application*.yml</include>
          <include>**/application*.yaml</include>
          <include>**/application*.properties</include>
        </includes>

<!--
一般的读取顺序是,yaml,yml,properties(可能不同版本yml在yaml前面,现在通常是yml在前面了,比如上面的顺序,但properties基本在后面)
后读取的那么相关参数会进行覆盖,没有的不会
如properties设置端口为8888端口
那么前面两个无论怎么设置端口,都是8888端口了,当然若有其他的操作,自然不会覆盖,因为properties并没有设置
假设都没有对应的配置,那么使用默认值,如都没有设置端口,那么就是8080端口(默认值)
-->
所以在某种程度上,我们建议使用一个配置文件,通常简单点,也就是yml,我们删除我们的properties的所有内容,修改后缀为yml
然后删除pojo包,再在controller对应的类中和测试类中,去掉关于之前数据库配置的代码,然后再删除数据库相关依赖,再在yml中添加如下:
案例:
server:
  port: 8080
  #设置起始路径,需要在项目前面加上hello才可,即访问http://localhost:8081/hello/hello/boot
  servlet:
  	#一般我们设置为/,大型的项目情况下可能会进行操作其他路径
    context-path: /hello 
启动项目,访问http://localhost:8080/hello/hello/boot,就有数据了
还有一些在yml中关于属性编写的介绍,但是这些细节太多,请到88章博客查看
配置文件属性值的注入:
使用Spring Boot全局配置文件设置属性时:
如果配置属性是Spring Boot已有属性,例如服务端口server.port,那么Spring Boot内部自动扫描并读取这些配置文件时,对应的属性值覆盖默认属性,如果配置的属性是用户自定义属性,例如刚刚自定义的Person实体类属性,则不会自动的覆盖,因为没有,那么他只是定义,并没有操作,需要我们在程序中手动注入这些配置属性方可操作,而不是自动的使用(因为定义)
那么实际上也可以这样的操作:如@Value(“${server.port}”),那么可以注入对应的端口值,因为虽然他覆盖了对应的默认属性,但他任然是定义的,既然是定义的,就可以使用
总体而言:该配置在spring boot中多了一个已有属性进行覆盖,其余的与普通的配置存放信息文件是一样的,需要被使用(如properties文件,以前有操作数据库的信息,那时就是被使用),然后使用注解注入对应的实例,当然若没有对应的注入配置属性,那么对应的实例自然是使用默认的值的,当然他们注入的方式基本都是扫描时进行操作的,只有扫描时,对应的注解操作才会进行
而对应的配置文件信息(写的信息)实际上也是使用后的再扫描的(在spring中也有注解和配置文件操作他,他基本是全局的)
Spring Boot支持多种注入配置文件属性的方式,下面来介绍如何使用注解@ConfigurationProperties和@Value注入属性
使用@ConfigurationProperties注入属性:
Spring Boot提供的@ConfigurationProperties注解
用来快速、方便地将配置文件中的自定义属性值批量注入到某个Bean对象的多个对应属性中
前面的操作中,我们就使用了这个注解并说明了,所以这里就不作说明
实际上上面的注解方式不够灵活,要想要更加的灵活(也就是不够更加的设置具体的细节),一般使用如下方式进行注入属性值
使用@Value注入属性:
@Value注解是Spring框架提供的,用来读取配置文件中的属性值并逐个注入到Bean对象的对应属性中
Spring Boot框架从Spring框架中对@Value注解进行了默认继承
所以在Spring Boot框架中还可以使用该注解读取和注入配置文件属性值,使用@Value注入属性的示例代码如下
@Value("${person.id}")    
private int id;
上述代码中,使用@Component和@Value注入Person实体类的id属性
其中,@Value不仅可以将配置文件的属性注入Person的id属性,还可以直接给id属性直接的赋值,如@Value(“1”),直接赋值为1
这点是@ConfigurationProperties不支持的,因为他只能去配置文件里加上对应的属性及其值才可,不够灵活,且使得配置文件信息变多
具体操作这里就不说明了
自定义配置:
spring Boot免除了项目中大部分的手动配置,对于一些特定情况,我们可以通过修改全局配置文件以适应具体生产环境,可以说,几乎所有的配置都可以写在application.yml文件中
Spring Boot会自动加载全局配置文件从而免除我们手动加载的烦恼(因为设置好的三个)
但是,如果我们自定义配置文件,Spring Boot是无法识别这些配置文件的(因为只有那三个可以),此时就需要我们手动加载
接下来,将针对Spring Boot的自定义配置文件及其加载方式进行讲解
使用@PropertySource加载配置文件:
对于这种加载自定义配置文件的需求,可以使用@PropertySource注解来实现,@PropertySource注解用于指定自定义配置文件的具体位置和名称,当然,如果需要将自定义配置文件中的属性值注入到对应类的属性中,可以使用@ConfigurationProperties或者@Value注解进行属性值注入,因为自定义配置文件与其他三个配置文件一样,都被读取操作了,自然结果是一样的,只是不会自动读取操作该自定义的配置文件而已,需要手动读取操作
现在我们演示:
打开Spring Boot项目的resources目录,在项目的类路径下新建一个test.properties自定义配置文件,在该配置文件中编写需要设置的配置属性
#对实体类对象MyProperties进行属性配置
test.id=110
test.name=test
在com.boot包下创建pojo包,然后创建一个配置类MyProperties
提供test.properties自定义配置文件中对应的属性,并根据@PropertySource注解的使用进行相关配置
package com.boot.pojo;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

@Component  // 自定义配置类
@PropertySource("classpath:test.properties")  // 指定自定义配置文件位置和名称,该文件的类型好像并不做要求
//好像只要对应的文件名称对应即可,到那时,一般是
//在扫描时,一般会先操作该@PropertySource注解,然后再操作其他的注解,使得可以操作属性值
@ConfigurationProperties(prefix = "test") // 指定配置文件注入属性前缀
public class MyProperties {
   
    private int id;
    private String name;

    @Override
    public String toString() {
   
        return "MyProperties{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }

    public int getId() {
   
        return id;
    }

    public void setId(int id) {
   
        this.id = id;
    }

    public String getName() {
   
        return name;
    }

    public void setName(String name) {
   
        this.name = name;
    }
}
进行测试:
@Autowired
    private MyProperties myProperties;
    
    @Test
    public void myPropertiesTest() {
   

        System.out.println(myProperties);
        //MyProperties{id=110, name='test'}
    }
使用@Configuration编写自定义配置类:
在Spring Boot框架中,推荐使用配置类的方式向容器中添加和配置组件
在Spring Boot框架中,通常使用@Configuration注解定义一个配置类
Spring Boot会自动扫描和识别配置类,从而替换传统Spring框架中的XML配置文件
当定义一个配置类后,还需要在类中的方法上使用@Bean注解进行组件配置,将方法的返回对象注入到Spring容器中
并且组件名称默认使用的是方法名,当然也可以使用@Bean注解的name或value属性自定义组件的名称
演示:
在项目下新建一个com.boot.config包,并在该包下新创建一个类MyConfig,该类中不需要编写任何代码
而该类目前没有添加任何配置和注解,因此还无法正常被Spring Boot扫描和识别
创建了一个com.boot.service包,里面创建空的MyService类,用来操作
接下来使用@Configuration注解将该MyConfig类声明一个配置类,内容如下:
@Configuration // 定义该类是一个配置类
public class MyConfig {
   

    @Bean    // 将返回值对象作为组件添加到Spring容器中,该组件id默认为方法名
    //当然也可以自己指定,如@Bean("myService2")
    public MyService myService(){
   
        return new MyService();
    }

}
MyConfig是@Configuration注解声明的配置类(类似于声明了一个XML配置文件)
该配置类会被Spring Boot自动扫描识别,使用@Bean注解的myService()方法
其返回值对象会作为组件添加到了Spring容器中(类似于XML配置文件中的标签配置),并且该组件的id默认是方法名myService
测试类:
@Autowired
    private MyService myService;
    @Autowired
    private MyConfig myConfig; //配置类也是会变成实例被使用的(加入对应的对象)

    @Test
    public void iocTest() {
   
        //返回结果,每次的运行一般都不会相同,因为对象(后面的就不在说明了)
        System.out.println(myService); //com.lagou.service.MyService@23564dd2
        System.out.println(myConfig);  	
        //com.lagou.config.MyConfig$$EnhancerBySpringCGLIB$$d53e0fdf@54895681
      
    }
若返回数据,则代表注入成功,当然也可以操作如下:
@Autowired
//包记得要对应,import org.springframework.context.ApplicationContext;
    private ApplicationContext applicationContext; 
//使用测试的注解,即测试的读取配置文件或者配置类(这里实际上也是),一般会将IOC容器本身放入自己的IOC容器中
//那么也就可以得到对应的ApplicationContext了(本身,即自己)

    @Test
    public void Test() {
   
        System.out.println(applicationContext.getBean("myService2"));
        System.out.println(applicationContext.containsBean("myService2")); //查看是否有该key
        //记得是:@Bean("myService2")
        
        /*
        com.lagou.service.MyService@200d1a3d
        true
		*/
    }
本质上是spring的知识,只不过spring boot整合了而已(spring boot只是对应注解使得操作了而已,也就是扫描)
如果硬要说的话,spring boot本身基本没有任何注解,就算有,也非常少,他的注解主要是他引入的依赖中spring造成的,他自身的具体作用就是依赖管理相关,和自动配置相关,当然了启动类的注解还是他的,只不过内部基本操作了spring的,当然,如果看包含关系,那么也可以说成spring注解是spring boot的
随机数设置及参数间引用:
在Spring Boot配置文件中设置属性时,除了可以像前面示例中显示的配置属性值外,还可以使用 随机值和参数间引用对属性值进行设置,下面,针对配置文件中这两种属性值的设置方式进行讲解
随机值设置:
在Spring Boot配置文件中,随机值设置使用到了Spring Boot内嵌的 RandomValuePropertySource类,对一些隐秘属性值或者测试用例属性值进行随机值注入,随机值设置的语法格式为${random.xx},xx表示需要指定生成的随机数类型和范围,它可以生成随机的整数、uuid或字符串,示例代码如下:
my.secret=${random.value}         // 配置随机值 
my.number=${random.int}           // 配置随机整数
my.bignumber=${random.long}     // 配置随机long类型数
my.uuid=${random.uuid}           // 配置随机uuid类型数
my.number.less.than.ten=${random.int(10)}   // 配置小于10的随机整数
my.number.in.range=${random.int[1024,65536]} // 配置范围在[1024,65536]之间的随机整数
上述代码中,使用RandomValuePropertySource类中random提供的随机数类型,分别展示了不同类型随机值的设置示例 ,本质上是读取配置时,识别形成的
我们来测试:
首先在pojo包下,创建MyTest类:
package com.boot.pojo;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "test")
public class MyTest {
   

    private int id;
    private String name;

    @Override
    public String toString() {
   
        return "MyTest{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }

    public int getId() {
   
        return id;
    }

    public void setId(int id) {
   
        this.id = id;
    }

    public String getName() {
   
        return name;
    }

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

我们可以测试,在yml(只有少部分与properties是不同的识别,这里肯定类似的)中加上如下:
test:
  id: ${
   random.int}
  name: ${
   random.int[1024,65536]}
  #int给String时,会自动的转换的
测试类:
  @Autowired
    private MyTest myTest;
    @Test
    public void fa(){
   
        System.out.println(myTest);
    }
多次的执行看看打印结果吧
参数间引用:
在Spring Boot配置文件中,配置文件的属性值还可以进行参数间的引用,也就是在后一个配置的属性值中直接引用先前已经定义过的属性,这样可以直接解析其中的属性值了, 使用参数间引用的好处就是,在多个具有相互关联的配置属性中,只需要对其中一处属性预先配置,其他地方都可以引用,省去了后续多处修改的麻烦,参数间引用的语法格式为${xx},xx表示先前在配置文件中已经配置过的属性名,示例代码如下:
app.name=MyApp
app.description=${app.name} is a Spring Boot application
我们继续修改上面的操作,在前面的yml中加上如下:
test:
  ui: 8
  id: ${
   random.int}
  name: ${
   random.int[1024,65536]}
  de: ${
   test.id} is ${
   test.ui}
在对应的MyTest类中加上如下:
private String de;

    public String getDe() {
   
        return de;
    }

    public void setDe(String de) {
   
        this.de = de;
    }

//修改toString
    @Override
    public String toString() {
   
        return "MyTest{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", de='" + de + '\'' +
                '}';
    }
继续访问测试类看看结果吧,这个时候你会发现一个问题,在上面的配置中:
test:
  ui: 8
  id: ${
   random.int}
  name: ${
   random.int[1024,65536]}
  de: ${
   test.id} is ${
   test.ui}
  #${test.id}和上面的值通常不同,但是后的${test.ui}是8,也就是说,这个调用是重新的赋值原来的处理,所以如果是固定值,自然赋值为8,如果是随机的,那么赋值随机的自然也会随机一次,所以${test.id}和上面的值通常不同
SpringBoot原理深入及源码剖析:
在前面88章博客中,这里的说明比较粗糙,那么这里我们来进行更加细节的说明
依赖管理:
为什么导入dependency时不需要指定版本,这是因为项目pom.xml文件有两个核心依赖,这里以前面为主:
 <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
首先我们先说明spring-boot-starter-parent
进入他,他里面配置一些基本的处理,通常有对应的三个配置文件,但是这不是我们需要的,我们进入他的:
<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>2.7.2</version>
    </parent>
核心代码具体如下:
 <properties>
    <activemq.version>5.16.5</activemq.version>
    <antlr2.version>2.7.7</antlr2.version>
    <appengine-sdk.version>1.9.98</appengine-sdk.version>
    <artemis.version>2.19.1</artemis.version>
    <aspectj.version>1.9.7</aspectj.version>
     
     ...
     
     
     <!--

定义了依赖的版本,所以我们引入其他相关依赖时可以不指定版本号,因为spring-boot-dependencies帮我们定义好了(这是maven自身的作用,因为是建立在maven上的)
但也要记住,他是有限的,也就是说,大多数的帮我们定义好了版本,但有些没有,但基本不会出现
因为这些版本都是Spring官方经过兼容性测试的,基本不会出现版本冲突或者兼容性的问题


-->
这是pom.xml引入依赖文件不需要标注依赖文件版本号的原因,这是依赖管理
spring-boot-starter-parent父依赖启动器的主要作用是进行版本统一管理,那么项目运行依赖的JAR包是从何而来的,很明显,就是对应的spring-boot-starter-web,在上面的spring-boot-dependencies后面是有对应的
所以在结合依赖管理以及其他自身存在的依赖操作,也就形成了我们的起步依赖,当然了,这里的起步并不是指spring boot原始依赖,是指功能加上后的,单纯来说spring boot对依赖的只有管理和部分起步的依赖
自动配置:
如果说依赖管理不算是spring boot的,那么这里绝对是,这是最重要的
在这里需要提一点,一般不同的版本的boot,对应的代码显示是不同的(上面的依赖管理的内容可能也会不同)
但大致底层原理是一样的,后面有时会说明一下,所以注意即可
概念:能够在我们添加jar包依赖的时候,自动为我们配置一些组件的相关配置
我们无需配置或者只需要少量配置就能运行编写的项目
Spring Boot到底是如何进行自动配置的,都把哪些组件进行了自动配置,这些的思考离不开我们补充的依赖,由于没有配置文件,也就是具体的xml,那么基本上都是操作配置类,而我们加上的依赖自然是存在配置类的,且由于是整合了spring boot,所以在满足约定的情况下,扫描的可以扫描到的,那么我们就只需要直到他是怎么扫描的即可
Spring Boot应用的启动入口是@SpringBootApplication注解标注的类中的main()方法
package com.boot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class BootApplication {
   

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

}

配置类自身是作为bean的,所以只需要考虑配置类的扫描处理,那么上面的注解是最重要的,我们看看他到底怎么操作扫描,或者他使得spring在什么时候操作扫描,我们直接的进入他:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.annotation.AliasFor;

@Target({
   ElementType.TYPE}) //注解的适用范围,Type表示注解可以描述在类、接口、注解或枚举中
@Retention(RetentionPolicy.RUNTIME) //表示注解的生命周期,Runtime(RUNTIME)表示运行时
@Documented //表示注解可以记录在javadoc中
@Inherited //表示可以被子类继承该注解
@SpringBootConfiguration // 标明该类为配置类
@EnableAutoConfiguration // 启动自动配置功能
@ComponentScan(
    excludeFilters = {
   @Filter(
    type = FilterType.CUSTOM,
    classes = {
   TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {
   AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
   
    // 根据class来排除特定的类,使其不能加入spring容器,传入参数value类型是class类型
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    Class<?>[] exclude() default {
   };

    @AliasFor(
        // 根据classname 来排除特定的类,使其不能加入spring容器,传入参数value类型是class的全类名字符串数组
        annotation = EnableAutoConfiguration.class
    )
    String[] excludeName() default {
   };

    // 指定扫描包,参数是包名的字符串数组
    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {
   };

    // 扫描特定的包,参数类似是Class类型数组
    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class<?>[] scanBasePackageClasses() default {
   };

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "nameGenerator"
    )
    Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

从上述源码可以看出,@SpringBootApplication注解是一个组合注解,前面 4 个是注解的元数据信息
我们主要看后面 3 个注解:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三个核心注解
关于这三个核心注解的相关说明具体如下(第三个:
@SpringBootConfiguration注解:
@SpringBootConfiguration:SpringBoot的配置类,标注在某个类上,表示这是一个SpringBoot的配置类
查看@SpringBootConfiguration注解源码,核心代码具体如下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.boot;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Indexed;

@Target({
   ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration // 配置类的作用等同于配置文件,配置类也是容器中的一个对象
@Indexed
public @interface SpringBootConfiguration {
   
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

从上述源码可以看出,@SpringBootConfiguration注解内部有一个核心注解@Configuration
该注解是Spring框架提供的,表示当前类为一个配置类(XML配置文件的注解表现形式),并可以被组件扫描器扫描,其中xml可以引入xml和扫描,配置类也可以引入配置类和扫描,而扫描(如:ComponentScan)也是可以扫描配置类的,当然,最终是谁是最开始的扫描,大概率是spring boot底层中进行处理的
由此可见,@SpringBootConfiguration注解的作用与@Configuration注解相同,都是标识一个可以被组件扫描器扫描的配置类
只不过@SpringBootConfiguration是被Spring Boot进行了重新封装命名而已,这样会使得该类可以被获取调用(底层获取执行),从而执行main方法
虽然我们也可以再次进行获取,但也要注意,同一个类里面,多个不同的变量(通常考虑同类型),基本是能注入同一个对象的
@EnableAutoConfiguration注解:
@EnableAutoConfiguration:开启自动配置功能,以前由我们需要配置的东西,现在由SpringBoot帮我们自动配置
这个注解就是Springboot能实现自动配置的关键
同样,查看该注解内部查看源码信息,核心代码具体如下 :
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值