spring-boot

SpringBoot

Spring Boot 是一个开源的 Java 框架,它基于 Spring 框架,旨在简化基于 Spring 的应用程序的开发和配置。以下是 Spring Boot 的几个关键特点和优势

  • 快速开发:Spring Boot 提供了开箱即用的配置,简化了项目的搭建过程,开发者可以在更短的时间内启动新的项目。
  • 自动配置:Spring Boot 能够根据项目中存在的依赖自动配置 Spring 应用程序,减少了大量的手动配置工作,使开发者能够专注于业务逻辑。
  • 独立的运行:Spring Boot 应用程序可以打成一个独立的 JAR 文件,包含内嵌的服务器(如 Tomcat、Jetty),这样可以轻松部署和运行。
  • 生产就绪特性:Spring Boot 提供了一些开箱即用的功能,帮助开发者开发和监控生产环境中的应用程序,包括健康检查、度量监控、外部配置管理等。
  • 广泛的生态系统:作为 Spring 的一部分,Spring Boot 可以无缝集成 Spring 生态系统中的其他项目(如 Spring Security、Spring Data、Spring Cloud 等),便于构建完整的企业级应用。
  • 约定优于配置:Spring Boot 常常遵循“约定优于配置”的原则,减少了大量的配置文件和复杂的 XML 配置,提高了开发效率。

特性:

  • 快速创建独立 Spring 应用
  • 直接嵌入Tomcat、Jetty or Undertow
  • 提供可选的 starter,简化应用整合
  • 按需自动配置 Spring 以及 第三方库
  • 提供生产级特性:如 监控指标、健康检查、外部化配置等
  • 无代码生成、无xml; 都是基于自动配置技术

总结:简化开发,简化配置,简化整合,简化部署,简化监控,简化运维

场景启动器

场景启动器:导入相关的场景,拥有相关的功能。

默认支持的所有场景:https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.build-systems.starters

  • 官方提供的场景:命名为:spring-boot-starter-*
  • 第三方提供场景:命名为:*-spring-boot-starter

依赖管理

maven父子继承,父项目可以锁定版本

<!--应用的父项目-->
	<parent>
	    <groupId>org.springframework.boot</groupId>
	    <artifactId>spring-boot-starter-parent</artifactId>
	    <version>1.5.9.RELEASE</version>
	</parent>
<!--我们继续追查spring-boot-starter-parent
最后发现最上层的父项目是-->
	<parent>
	  <groupId>org.springframework.boot</groupId>
	  <artifactId>spring-boot-dependencies</artifactId>
	  <version>1.5.9.RELEASE</version>
	  <relativePath>../../spring-boot-dependencies</relativePath>
	</parent>
<!---这才是真正的项目管理者,他指定了所有需要的jar包版本依赖->

如何修改默认版本号

  • version标签 精确声明版本
 <dependency>
   <groupId>com.github.pagehelper</groupId>
   <artifactId>pagehelper</artifactId>
   <version>6.1.0</version>
</dependency>
  • 子覆盖父的属性设置 mysql.version
<properties>
    <java.version>17</java.version>
    <mysql.version>8.0.3</mysql.version>
</properties>

自动配置 - 完整流程

image-20240910103330123

自动配置流程细节梳理:

1、导入starter-web:导入了web开发场景

  • 1、场景启动器导入了相关场景的所有依赖:starter-jsonstarter-tomcatspringmvc
  • 2、每个场景启动器都引入了一个spring-boot-starter,核心场景启动器。
  • 3、核心场景启动器引入了spring-boot-autoconfigure包。
  • 4、spring-boot-autoconfigure里面囊括了所有场景的所有配置。
  • 5、只要这个包下的所有类都能生效,那么相当于SpringBoot官方写好的整合功能就生效了。
  • 6、SpringBoot默认却扫描不到 spring-boot-autoconfigure下写好的所有配置类。(这些配置类给我们做了整合操作),默认只扫描主程序所在的包

2、主程序:@SpringBootApplication

  • 1、@SpringBootApplication由三个注解组成@SpringBootConfiguration@EnableAutoConfiguratio@ComponentScan

  • 2、SpringBoot默认只能扫描自己主程序所在的包及其下面的子包,扫描不到 spring-boot-autoconfigure包中官方写好的配置类

  • 3、**@EnableAutoConfiguration**:SpringBoot 开启自动配置的核心

      1. 是由@Import(AutoConfigurationImportSelector.class)提供功能:批量给容器中导入组件。
      1. SpringBoot启动会默认加载 142个配置类。
      1. 142个配置类来自于spring-boot-autoconfigureMETA-INF/spring/**org.springframework.boot.autoconfigure.AutoConfiguration**.imports文件指定的
    • 项目启动的时候利用 @Import 批量导入组件机制把 autoconfigure 包下的142 xxxxAutoConfiguration类导入进来(自动配置类
    • 虽然导入了142个自动配置类
  • 4、按需生效:

    • 并不是这142个自动配置类都能生效
    • 每一个自动配置类,都有条件注解@ConditionalOnxxx,只有条件成立,才能生效

3、**xxxxAutoConfiguration**自动配置类

  • 1、给容器中使用@Bean 放一堆组件。
  • 2、每个自动配置类都可能有这个注解@EnableConfigurationProperties(**ServerProperties**.class),用来把配置文件中配的指定前缀的属性值封装到 xxxProperties属性类
  • 3、以DataSourceAutoConfiguration为例:所有配置都是以spring.datasource开头的,配置都封装到了属性类中。
  • 4、给容器中放的所有组件的一些核心参数,都来自于**xxxProperties****xxxProperties**都是和配置文件绑定。
  • 只需要改配置文件的值,核心组件的底层参数都能修改

4、写业务,全程无需关心各种整合(底层这些整合写好了,而且也生效了)

效果:

  • 修改配置文件,修改底层参数

    所有场景自动配置好直接使用

    可以注入SpringBoot配置好的组件随时使用

属性绑定

将容器中任意组件的属性值和配置文件的配置项的值进行绑定

@ConfigurationProperties 是 Spring Framework 中的一个注解,用于将外部配置(如应用程序的属性文件、环境变量、系统属性等)绑定到 Java 对象上。这样可以方便地管理和使用配置参数

  • 给容器中注册组件(@Component、@Bean)

定义属性类

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

@Component  
@ConfigurationProperties(prefix = "app") //prefix配置文件中以什么为前缀的配置项 
public class AppProperties {  
    private String name;  
    private String version;  

    // getters and setters  
    public String getName() {  
        return name;  
    }  

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

    public String getVersion() {  
        return version;  
    }  

    public void setVersion(String version) {  
        this.version = version;  
    }  
}
  • 使用 @ConfigurationProperties 声明组件和配置文件的哪些配置项进行绑定

在配置文件中定义属性 (application.propertiesapplication.yml):

app.name=MyApplication  
app.version=1.0.0

YAML 文件

YAML(YAML Ain't Markup Language)是一种人类可读的数据序列化格式,广泛用于配置文件、数据交换和存储。以下是 YAML 文件的详细介绍,包括其基本语法、结构和常见用法。

基本语法

缩进:YAML 使用空格进行缩进,通常使用两个空格。不能使用制表符(Tab)。

键值对:使用冒号(:)分隔键和值。

name: John Doe  
age: 30  

列表:使用短横线(-)表示列表项。

fruits:  
  - apple  
  - banana  
  - orange  

字典:可以嵌套字典。

person:  
  name: John Doe  
  age: 30  

注释:使用井号(#)添加注释。

# 这是一个注释  
name: John Doe  # 这是另一个注释  

示例 YAML 文件

# 如果yaml和properties 同时出现相同配置,则properties的优先级更高
person:
  name: 张三
  age: 20
  birth-day: 2019/03/04 12:00:00
  like: true
  child:
    name: 李四
    age: 18
    birth-day: 2019/03/04 12:00:00
    text: ["haa", "bbb", "ccc"] #数组
  dogs:
    - name: 旺财
      age: 1
    - name: 旺财2
      age: 2
    - {name: 旺财3, age: 3} #对象
  cats:
    bluecat:
      name: 蓝猫
      age: 1
    redcat:
      name: 红猫
      age: 2
    blackcat: {name: 黑猫, age: 3} #map

SpringApplication

自定义 banner:类路径添加banner.txt设置spring.banner.location就可以定制 banner

banner地址: https://www.bootschool.net/ascii

自定义 SpringApplication

new SpringApplication

//自动配置
@SpringBootApplication
public class Springboot01DemoApplication {
    public static void main(String[] args) {
        //1、创建SpringApplication对象
        SpringApplication application = new SpringApplication(Springboot01DemoApplication.class);
        //2、启动
        application.run(args);

    }

}

new SpringApplicationBuilder

//自动配置
@SpringBootApplication
public static void main(String[] args) {
		SpringApplicationBuilder builder = new SpringApplicationBuilder();

        //链式调用
        builder.sources(Springboot01DemoApplication.class)
               .run(args);

    }

}

日志系统

在 Spring Boot 应用程序中,日志系统是一个重要的组成部分,用于记录应用程序的运行状态、错误信息和调试信息。Spring Boot 默认集成了多种日志框架,并提供了简单的配置方式。

日志系统-日志框架

Spring Boot 默认使用 Logback 作为其日志框架。Logback 是一个功能强大且灵活的日志框架,支持多种日志输出方式。

SpringBoot 默认使用 slf4j + logback

除了 Logback,Spring Boot 还支持其他日志框架。

image-20240909190710982

使用方式

获取一个日志记录器

//格式: 时间  级别  进程id --- 项目名 --- 线程名 --- 当前类名: 日志内容
Logger logger = LoggerFactory.getLogger(LogTest.class);
logger.trace("trace");
logger.debug("debug");
logger.info("info");
logger.warn("warn");
logger.error("error");

注解方式:@Slf4j

@Slf4j
@SpringBootTest
class ApplicationTests {
    @Test
    void test02() {
        //级别:由低到高:ALL -- TRACE -- DEBUG -- INFO -- WARN -- ERROR -- OFF
        //越打印,越粗糙; 日志有一个默认级别(INFO);只会打印这个级别之上的所有信息;
        log.trace("trace");
        log.debug("debug");
        log.info("info");
        log.warn("warn");
        log.error("error");
        
        //日志占位符{}
        log.info("{}",值)
    }
}

日志系统 - 日志格式

默认输出格式:
时间和日期:毫秒级精度
日志级别:ERROR, WARN, INFO, DEBUG,TRACE.
进程 ID
---: 消息分割符
线程名: 使用[]包含
Logger 名: 通常是产生日志的全类名
消息: 日志记录的内容

注意: logback 没有FATAL级别,对应的是ERROR

image-20240909194153958

日志系统 - 日志级别

由低到高:ALL,TRACE, DEBUG, INFO, WARN, ERROR,FATAL,OFF;

只会打印指定级别及以上级别的日志

  • ALL:打印所有日志
  • TRACE:追踪框架详细流程日志,一般不使用
  • DEBUG:开发调试细节日志
  • INFO:关键、感兴趣信息日志
  • WARN:警告但不是错误的信息日志,比如:版本过时
  • ERROR:业务错误日志,比如出现各种异常
  • FATAL:致命错误日志,比如jvm系统崩溃
  • OFF:关闭所有日志记录

不指定级别的所有类,都使用 root 指定的级别作为默认级别==>SpringBoot日志默认级别是 INFO

指定日志级别

指定类的日志级别

#指定Application类的中的日志级别为debug
logging.level.com.chs.springboot.Application=debug

指定包下所有的日志级别

#指定mapper包的中所有的日志级别为debug
logging.level.com.chs.mapper.**=debug

指定root 的日志级别

logging.level.root=error

指定web的日志级别

logging.level.web=error

指定sql的日志级别

logging.level.sql=error

日志系统 - 日志分组

在 Spring Boot 2.4 及更高版本中,引入了日志分组的功能,允许您将多个包组合在一起,以便于管理和配置。这种方式可以让您更简单地为多个类或包设置相同的日志级别。

# 定义一个名为 tomcat 的日志组,该组将包含以下三个包  
logging.group.tomcat=org.apache.catalina,org.apache.coyote,org.apache.tomcat  

# 为 tomcat 日志组设置日志级别  
logging.level.tomcat=trace

SpringBoot 预定义两个组

组名 范围
web org.springframework.core.codec, org.springframework.http, org.springframework.web, org.springframework.boot.actuate.endpoint.web, org.springframework.boot.web.servlet.ServletContextInitializerBeans
sql org.springframework.jdbc.core, org.hibernate.SQL, org.jooq.tools.LoggerListener

日志系统 - 文件输出

SpringBoot 默认只把日志写在控制台,如果想额外记录到文件,可以在application.properties中添加 logging.file.name 或 logging.file.path 配置项。

logging.file.name

功能: 该属性用于指定日志文件的名称。它允许你直接定义日志文件的完整路径和文件名。

# 将日志输出到特定的文件  
logging.file.name=/var/log/myapp.log  

在这个示例中,所有的日志信息将被输出到 /var/log/myapp.log 文件中。如果该文件路径不存在,Spring Boot 会默认创建该文件。

logging.file.path

功能: 该属性用于指定日志文件的存储目录,而文件名则通过默认命名(spring.log)规则来生成。日志文件的名称通常为应用程序名称,并附加日期。

# 设置日志文件的存储路径  
logging.file.path=/var/log/myapp/  

如果同时设置了 logging.file.namelogging.file.path,Spring Boot 会优先使用 logging.file.name 所指定的完整路径和文件名。

日志系统 - 文件归档与滚动切割

归档:每天的日志单独存到一个文档中。

切割:每个文件10MB,超过大小切割成另外一个文件。

配置项 描述
logging.logback.rollingpolicy.file-name-pattern 日志存档的文件名格式 默认值:${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz
logging.logback.rollingpolicy.clean-history-on-start 应用启动时是否清除以前存档;默认值:false
logging.logback.rollingpolicy.max-file-size 每个日志文件的最大大小;默认值:10MB
logging.logback.rollingpolicy.total-size-cap 日志文件被删除之前,可以容纳的最大大小(默认值:0B)。设置1GB则磁盘存储超过 1GB 日志后就会删除旧日志文件
logging.logback.rollingpolicy.max-history 日志文件保存的最大天数;默认值:7

注意:环境兼容性: 在部署到不同环境(如开发、测试、生产)时,可以通过不同的配置文件(如 application-dev.properties)来设定不同的日志路径和文件名。

  • 开发:application-dev.properties
  • 测试:application-test.properties
  • 生产:application-prod.properties

日志系统 - 自定义配置

在 Spring Boot 中,可以通过自定义配置来满足特定日志记录需求。Spring Boot 默认使用 Logback 作为日志框架,但你可以通过配置文件对其进行详细定制。

日志系统 自定义
Logback logback-spring.xml / logback.xml
Log4j2 log4j2-spring.xml / log4j2.xml
JDK (Java Util Logging) logging.properties

src/main/resources 目录下创建一个名为 logback.xml 的文件。这个文件将用于配置 Logback 的行为。

<?xml version="1.0" encoding="UTF-8"?>
<!--
scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒当scan为true时,此属性生效。默认的时间间隔为1分钟。
debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。
-->
<configuration scan="false" scanPeriod="60 seconds" debug="false">
    <!-- 定义日志的根目录 -->
    <property name="LOG_HOME" value="/logs/potato"/>
    <!-- 定义日志文件名称 -->
    <property name="APP_NAME" value="com.potato.webmarket"></property>
    <!-- ch.qos.logback.core.ConsoleAppender 表示控制台输出 -->
    <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
        <!--
        日志输出格式:
   %d表示日期时间,
   %thread表示线程名,
   %-5level:级别从左显示5个字符宽度
   %logger{50} 表示logger名字最长50个字符,否则按照句点分割。
   %msg:日志消息,
   %n是换行符
        -->
        <layout class="ch.qos.logback.classic.PatternLayout">
<!--            &lt;!&ndash;开发环境 日志输出格式&ndash;&gt;-->
<!--            <springProfile name="dev">-->
<!--                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} &#45;&#45;&ndash;&gt; [%thread] -&ndash;&gt; %-5level %logger{50} - %msg%n</pattern>-->
<!--            </springProfile>-->
<!--            &lt;!&ndash;非开发环境 日志输出格式&ndash;&gt;-->
<!--            <springProfile name="!dev">-->
<!--                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} ==== [%thread] ==== %-5level %logger{50} - %msg%n</pattern>-->
<!--            </springProfile>-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread]**************** %-5level %logger{50} - %msg%n</pattern>
        </layout>
    </appender>
    <!-- 滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->
    <appender name="appLogAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 指定日志文件的名称 -->
        <file>${LOG_HOME}/${appName}.log</file>
        <!--
        当发生滚动时,决定 RollingFileAppender 的行为,涉及文件移动和重命名
        TimeBasedRollingPolicy: 最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动。
        -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--
            滚动时产生的文件的存放位置及文件名称 %d{yyyy-MM-dd}:按天进行日志滚动
            %i:当文件大小超过maxFileSize时,按照i进行文件滚动
            -->
            <fileNamePattern>${LOG_HOME}/${appName}-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
            <!--
            可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件。假设设置每天滚动,
            且maxHistory是365,则只保存最近365天的文件,删除之前的旧文件。注意,删除旧文件是,
            那些为了归档而创建的目录也会被删除。
            -->
            <MaxHistory>365</MaxHistory>
            <!--
            当日志文件超过maxFileSize指定的大小是,根据上面提到的%i进行日志文件滚动 注意此处配置SizeBasedTriggeringPolicy是无法实现按文件大小进行滚动的,必须配置timeBasedFileNamingAndTriggeringPolicy
            -->
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <!-- 日志输出格式: -->
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [ %thread ] ------------------ [ %-5level ] [ %logger{50} : %line ] -
                %msg%n
            </pattern>
        </layout>
    </appender>
    <!--
  logger主要用于存放日志对象,也可以定义日志类型、级别
  name:表示匹配的logger类型前缀,也就是包的前半部分
  level:要记录的日志级别,包括 TRACE < DEBUG < INFO < WARN < ERROR
  additivity:作用在于children-logger是否使用 rootLogger配置的appender进行输出,
  false:表示只用当前logger的appender-ref,true:
  表示当前logger的appender-ref和rootLogger的appender-ref都有效
    -->
    <!-- hibernate logger -->
    <logger name="com.potato.marketweb" level="info"/>
    <!-- Spring framework logger -->
    <logger name="org.springframework" level="info" additivity="false"></logger>
    <!--
    root与logger是父子关系,没有特别定义则默认为root,任何一个类只会和一个logger对应,
    要么是定义的logger,要么是root,判断的关键在于找到这个logger,然后判断这个logger的appender和level。
    -->
    <root level="info">
        <appender-ref ref="stdout"/>
<!--        <appender-ref ref="appLogAppender"/>-->
    </root>
</configuration>

如果在 src/main/resources 目录下有名为 logback.xml 的文件,日志就会按照自定义的类型打印

<configuration>

    <!-- 控制台应用器 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} ~~~~~~~~ %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 文件应用器 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/myapp.log</file>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} ~~|||~~~ [%thread] %-5level %logger{36} ~~~~~ %msg%n</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logs/myapp.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
    </appender>

    <!-- 日志级别 -->
    <root level="info">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="FILE" />
    </root>

</configuration>

日志系统 – 最佳实践

1、导入任何第三方框架,先排除它的日志包,因为Boot底层控制好了日志
2、修改 application.properties 配置文件,就可以调整日志的所有行为。如果不够,可以编写日志框架自己的配置文件放在类路径下就行,比如logback-spring.xml,log4j2-spring.xml

3、如需对接专业日志系统,也只需要把 logback 记录的日志灌倒 kafka之类的中间件,这和SpringBoot没关系,都是日志框架自己的配置,修改配置文件即可

4、业务中使用slf4j-api记录日志。不要再 sout 了

Profiles环境隔离 - 基础用法

通常我们开发不可能只有一个生产环境,还会有其它的开发,测试,预发布环境等等。为了更好的管理每个环境的配置项,springboot也提供了对应的环境隔离的方法。

1. 标识环境:指定哪些组件、配置在哪个环境生效 =>@Profile 标记组件生效环境

2. 切换环境:这个环境对应的所有组件和配置就应该生效

激活环境方法

  • 在application.properties或者yaml文件中配置:spring.profiles.active=环境1,环境2
  • 命令行激活:java -jar xxx.jar --spring.profiles.active=环境1,环境2

环境包含

  • spring.profiles.include[0]=环境1
  • spring.profiles.include[1]=环境2

生效的配置 = 默认环境配置 + 激活的环境 + 包含的环境配置

项目里面这么用

  • 基础的配置mybatis、log、xxx:写到包含环境中
  • 需要动态切换变化的 db、redis:写到激活的环境中

Profiles环境隔离 - 分组

创建 prod 组,指定包含 db 和 mq 配置

  • spring.profiles.group.prod[0]=db
  • spring.profiles.group.prod[1]=mq

使用 --spring.profiles.active=prod ,激活prod,db,mq配置文件

spring.profiles.group.prod[0]=db
spring.profiles.group.prod[1]=mq
spring.profiles.active=prod

Profiles环境隔离 - 配置文件

application-{profile}.properties 可以作为指定环境的配置文件

激活这个环境,配置就会生效。最终生效的所有配置是

  • application.properties:主配置文件,任意时候都生效
  • application-{profile}.properties:指定环境配置文件,激活指定环境生效

profile优先级 > application

spring.profiles.active=dev #设置环境为dev环境

image-20240909211212221

生效的配置文件有:主配置文件和dev环境的配置文件

外部化配置

在 Spring Boot 中,外部化配置允许你将应用程序的配置信息与代码分离,使得在不同环境(如开发、测试、生产)中能方便地修改配置而无需重新编译应用程序。

外部配置优先于内部配置

可以在打包好后的jar包同级目录创建application.properties;

可以在打包好后的jar包同级目录创建config目录,在目录里面创建application.properties;

可以在打包好后的jar包同级目录创建config目录在config目录里面再创建一个目录,在这个目录里面创建application.properties;

命令行>config/*/application.properties;>config/application.properties;>application.properties;

image-20240909211723237

单元测试 - 测试注解

@Test :表示方法是测试方法。

@ParameterizedTest :表示方法是参数化测试,下方会有详细介绍

@RepeatedTest :表示方法可重复执行,下方会有详细介绍

@DisplayName :为测试类或者测试方法设置展示名称

@BeforeEach :表示在每个单元测试之前执行

@AfterEach :表示在每个单元测试之后执行

@BeforeAll :表示在所有单元测试之前执行

@AfterAll :表示在所有单元测试之后执行

@Tag :表示单元测试类别,类似于JUnit4中的@Categories

@Disabled :表示测试类或测试方法不执行,类似于JUnit4中的@Ignore

@Timeout :表示测试方法运行如果超过了指定时间将会返回错误

@ExtendWith :为测试类或测试方法提供扩展类引用

单元测试 - 断言机制

方法 说明
assertEquals 判断两个对象或两个原始类型是否相等
assertNotEquals 判断两个对象或两个原始类型是否不相等
assertSame 判断两个对象引用是否指向同一个对象
assertNotSame 判断两个对象引用是否指向不同的对象
assertTrue 判断给定的布尔值是否为 true
assertFalse 判断给定的布尔值是否为 false
assertNull 判断给定的对象引用是否为 null
assertNotNull 判断给定的对象引用是否不为 null
assertArrayEquals 数组断言
assertAll 组合断言
assertThrows 异常断言
assertTimeout 超时断言
fail 快速失败

断言:判断字符串是否等于hello

@Slf4j
@SpringBootTest
class ApplicationTests {
    @Test
    public void test01(){
        String str = "hello";
        Assertions.assertEquals("hello",str); //断言:判断字符串是否等于hellollo
    }
}

可观测性

可观测性(Observability)是指应用程序在生产环境中运行时,能够收集和分析其内部状态与行为的能力。它使得开发和运维团队能够有效监控应用性能,诊断问题,并在出现异常时采取措施。

image-20240909220210475

可观测性的核心组成部分

  1. 日志(Logging):
    • 日志是记录应用程序运行情况的重要手段,通常包括信息、警告、错误等各种级别的日志。
    • 日志应详细且结构化,以便于后续分析和查询。
  2. 指标(Metrics):
    • 指标是应用程序量化性能和健康状况的数值,例如请求处理时间、系统负载、内存使用情况等。
    • 指标通常以时间序列的方式存储,并可以通过可视化工具(如Grafana、Prometheus)进行展示。
  3. 追踪(Tracing):
    • 追踪用于分析请求在系统中流动的路径,从而了解各个组件之间的调用关系和延迟。
    • 分布式追踪工具(如OpenTelemetry、Jaeger)能够帮助开发者跟踪跨多个服务的请求处理流程。

SpringBoot 提供了 actuator 模块,可以快速暴露应用的所有指标

导入: spring-boot-starter-actuator

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

配置配置文件

management.endpoints.web.exposure.include=*

访问 http://localhost:8080/actuator;展示出所有可以用的监控端点

可观测性 - Endpoints

端点名 描述
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 执行线程转储
heapdump 返回hprof堆转储文件
jolokia 通过HTTP暴露JMX bean(需要引入Jolokia,不适用于WebFlux)。需要引入依赖jolokia-core
logfile 返回日志文件的内容(如果已设置logging.file.name或logging.file.path属性)。支持使用HTTPRange标头来检索部分日志文件的内容
prometheus 以Prometheus服务器可以抓取的格式公开指标。需要依赖micrometer-registry-prometheus

Spring-Boot生命周期监听

image-20240909224641211

SpringApplicationRunListener

SpringApplicationRunListener 是 Spring 框架中的一个接口,它是 Spring Boot 的启动过程中的一个重要扩展点。通过实现这个接口,开发人员可以在 Spring Boot 应用启动的不同阶段执行特定的逻辑。这对于需要在应用启动之前或期间进行某些初始化工作、环境配置、预处理等需求的场景非常有用。

pringApplicationRunListener 接口定义了一些方法,主要包括:

starting:当 Spring 应用上下文(SpringApplication)开始启动时调用。

environmentPrepared:在环境(Environment)准备好之后被调用,允许开发者在应用运行之前对环境进行调整。

contextPrepared:当应用上下文准备好,但尚未刷新时调用。这是一个可以进行上下文配置和初始化的机会。

contextLoaded:当应用上下文加载后(即上下文已刷新完成时)调用。这时已经可以访问上下文中的各种 Bean。

finished:当应用启动完成并且上下文被完全加载和初始化后调用。这通常是释放资源或执行最后的清理工作的位置。

使用示例(全阶段 )

实现接口SpringApplicationRunListener并在构造函数中处理 SpringApplicationString[] 类型的参数。

实现方法:重写需要的方法以添加自定义逻辑。

import org.springframework.boot.SpringApplication;  
import org.springframework.boot.SpringApplicationRunListener;  
import org.springframework.context.ApplicationListener;  
import org.springframework.context.ConfigurableApplicationContext;  

public class CustomSpringApplicationRunListener implements SpringApplicationRunListener {  

    public CustomSpringApplicationRunListener(SpringApplication application, String[] args) {  
        // 构造函数,进行初始化  
    }  

    @Override  
    public void starting() {  
        System.out.println("Application is starting...");  
    }  

    @Override  
    public void environmentPrepared(SpringApplication application, ConfigurableEnvironment environment) {  
        System.out.println("Environment is prepared.");  
    }  

    @Override  
    public void contextPrepared(ConfigurableApplicationContext context) {  
        System.out.println("Application context is prepared.");  
    }  

    @Override  
    public void contextLoaded(ConfigurableApplicationContext context) {  
        System.out.println("Application context is loaded.");  
    }  

    @Override  
    public void finished(ConfigurableApplicationContext context, SpringApplicationExitCodeGenerator exitCodeGenerator) {  
        System.out.println("Application has finished running.");  
    }  
}

注册 Listener:在 src/main/resources/META-INF/spring.factories 文件中添加如下内容以注册自定义的监听器:

org.springframework.boot.SpringApplicationRunListener=\
com.example.CustomSpringApplicationRunListener

生命周期监听器

监听器 感知阶段 配置方式
BootstrapRegistryInitializer 特定阶段:引导初始化 1、META-INF/spring.factories 2、application.addBootstrapRegistryInitializer()
ApplicationContextInitializer 特定阶段:ioc容器初始化 1、META-INF/spring.factories 2、application.addInitializers()
ApplicationListener 全阶段 1、META-INF/spring.factories
2、SpringApplication.addListeners(…)
3、@Bean 或 @EventListener
SpringApplicationRunListener 全阶段 META-INF/spring.factories
ApplicationRunner 特定阶段:感知应用就绪 @Bean
CommandLineRunner 特定阶段:感知应用就绪 @Bean

最佳实践:

1、应用启动后做事:ApplicationRunner、CommandLineRunner

2、事件驱动开发:ApplicationListener

CommandLineRunner(感知应用就绪)

CommandLineRunner 是 Spring Boot 提供的一个功能接口,可以在 Spring Boot 应用程序启动后执行特定的逻辑。它允许开发者在应用启动时根据命令行参数执行代码,通常用于执行初始化任务、加载数据或启动某些服务。

使用方法

实现接口:创建一个类并实现 CommandLineRunner 接口。

重写 run 方法:在 run 方法中编写你希望在应用启动时执行的逻辑。

注册为 Spring Bean:将该类声明为 Spring Bean (例如使用 @Component 注解),Spring 会自动在应用启动时调用它的 run 方法。

import org.springframework.boot.CommandLineRunner;  
import org.springframework.stereotype.Component;  

@Component  
public class MyCommandLineRunner implements CommandLineRunner {  

    @Override  
    public void run(String... args) throws Exception {  
        System.out.println("Application started with command-line arguments: " + String.join(", ", args));  
        // 这里可以执行其他的初始化逻辑,例如存储数据到数据库  
    }  
}

Spring-Boot生命周期事件

Spring Boot生命周期事件是指在Spring Boot应用程序的不同阶段(例如启动、运行和停止)中,Spring框架触发的特定事件。

主要生命周期事件

ApplicationStartedEvent:应用程序启动后触发的事件。通常在应用上下文被创建后但尚未刷新时发生。

ApplicationEnvironmentPreparedEvent:应用环境准备好后触发。可以在此事件中根据需要修改环境属性。

ApplicationContextInitializedEvent:应用上下文初始化后触发。所有的bean定义已经加载,但上下文还没有刷新。

ApplicationPreparedEvent:应用准备好后触发。此时应用上下文已经准备好,但尚未刷新。

ApplicationStartedEvent:应用程序已完全启动并且所有的beans都已经实例化的事件。

ApplicationReadyEvent:应用程序已经完全启动,且上下文已经完全刷新,所有的beans也已经准备好。此时可以进行处理请求等操作。

ApplicationFailedEvent:应用启动失败时触发的事件。可以在此事件中进行错误处理或记录。

ApplicationReadyEvent:应用程序已启动并准备接受请求后触发的事件。这是一个很好的时机来启动那些依赖于应用程序的功能。

ApplicationContextClosedEvent:当应用程序上下文关闭时触发的事件。该事件在所有的beans都被销毁后触发。

image-20240909231253388

image-20240909234248404

事件驱动开发

定义事件:

  • 任意事件:任意类可以作为事件类,建议命名 xxxEvent
@Data //定义一个事件
public class SendSuccessEvent {
    private String message;
    public SendSuccessEvent(String message) {
        this.message = message;
    }
}
  • 系统事件:继承 ApplicationEvent的类的实例
import org.springframework.context.ApplicationEvent;  
public class CustomEvent extends ApplicationEvent {  
    private String message;  
    public CustomEvent(Object source, String message) {  
        super(source);  
        this.message = message;  
    }  
    public String getMessage() {  
        return message;  
    }  
}

事件发布:

  • 组件实现 ApplicationEventPublisherAware
import org.springframework.context.ApplicationEventPublisher;  
import org.springframework.context.ApplicationEventPublisherAware;  
import org.springframework.stereotype.Component;  

@Component  
public class UserService implements ApplicationEventPublisherAware {  
    private ApplicationEventPublisher eventPublisher;  
    @Override  
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {  
        this.eventPublisher = applicationEventPublisher;  
    }  
    public void registerUser(String message) {  
        // 发布事件  
        UserRegisteredEvent event = new UserRegisteredEvent(this, message);  
        eventPublisher.publishEvent(event);  
    }  
}
  • 自动注入 ApplicationEventPublisher
@Slf4j
@RestController
public class UserController {
    @Autowired
    ApplicationEventPublisher publisher;
    //同步阻塞式;
    //事件/消息驱动;
    @PutMapping("/send")
    public String login(String message){
        //做事;同步调用
        SendSuccessEvent event = new SendSuccessEvent(message);
        //1.发送事件
        publisher.publishEvent(event);
        
        // 2.发布事件  
        UserRegisteredEvent event = new UserRegisteredEvent(this, message);  
        eventPxublisher.publishEvent(event);  
        
        return "发送成功";
    }
}

监听事件

通过实现 ApplicationListener 接口,或者使用 @EventListener 注解的方式。

组件 + 方法标注@EventListener

@Slf4j
@Service
public class UserPointsService {
    @Async //异步
    @EventListener(SendSuccessEvent.class)
    public void listen(SendSuccessEvent event){
        log.info("监听到 SendSuccessEvent 事件");
        givePoints(event.getmessage());
    }
    public void givePoints(String message) {
        log.info(message);
    }
}

通过实现 ApplicationListener 接口

import org.springframework.context.ApplicationListener;  
import org.springframework.stereotype.Component;  

@Component  
public class UserRegisteredListener implements ApplicationListener<UserRegisteredEvent> {  
    @Override  
    public void onApplicationEvent(UserRegisteredEvent event) { 
        //event 传递的消息
        //事件处理逻辑   
    }  
}

异步事件处理

结合 Spring 的异步处理机制,可以实现事件的异步发布和处理,提高应用的响应能力。

@Async //异步

自动配置原理

入门理解

应用关注的三大核心场景配置组件

image-20240910105742214

自动配置流程细节梳理:

1、导入starter-web:导入了web开发场景

  • 1、场景启动器导入了相关场景的所有依赖:starter-jsonstarter-tomcatspringmvc
  • 2、每个场景启动器都引入了一个spring-boot-starter,核心场景启动器。
  • 3、核心场景启动器引入了spring-boot-autoconfigure包。
  • 4、spring-boot-autoconfigure里面囊括了所有场景的所有配置。
  • 5、只要这个包下的所有类都能生效,那么相当于SpringBoot官方写好的整合功能就生效了。
  • 6、SpringBoot默认却扫描不到 spring-boot-autoconfigure下写好的所有配置类。(这些配置类给我们做了整合操作),默认只扫描主程序所在的包

2、主程序:@SpringBootApplication

  • 1、@SpringBootApplication由三个注解组成@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan

  • 2、SpringBoot默认只能扫描自己主程序所在的包及其下面的子包,扫描不到 spring-boot-autoconfigure包中官方写好的配置类

  • 3、**@EnableAutoConfiguration**:SpringBoot 开启自动配置的核心

      1. 是由@Import(AutoConfigurationImportSelector.class)提供功能:批量给容器中导入组件。
      1. SpringBoot启动会默认加载 152个配置类。
      1. 152个配置类来自于spring-boot-autoconfigureMETA-INF/spring/**org.springframework.boot.autoconfigure.AutoConfiguration**.imports文件指定的
    • 项目启动的时候利用 @Import 批量导入组件机制把 autoconfigure 包下的152 xxxxAutoConfiguration类导入进来(自动配置类
    • 虽然导入了152个自动配置类
  • 4、按需生效:

    • 并不是这152个自动配置类都能生效
    • 每一个自动配置类,都有条件注解@ConditionalOnxxx,只有条件成立,才能生效

3、**xxxxAutoConfiguration**自动配置类

  • 1、给容器中使用@Bean 放一堆组件。
  • 2、每个自动配置类都可能有这个注解@EnableConfigurationProperties(**ServerProperties**.class),用来把配置文件中配的指定前缀的属性值封装到 xxxProperties属性类
  • 3、以DataSourceAutoConfiguration为例:所有配置都是以spring.datasource开头的,配置都封装到了属性类中。
  • 4、给容器中放的所有组件的一些核心参数,都来自于**xxxProperties****xxxProperties**都是和配置文件绑定。
  • 只需要改配置文件的值,核心组件的底层参数都能修改

4、写业务,全程无需关心各种整合(底层这些整合写好了,而且也生效了)

核心流程总结:

  1. 导入 starter,就会导入autoconfigure 包。
  2. autoconfigure 包里面 有一个文件 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,里面指定的所有启动要加载的自动配置类
  3. @EnableAutoConfiguration 会自动的把上面文件里面写的所有自动配置类都导入进来。xxxAutoConfiguration 是有条件注解进行按需加载
  4. xxxAutoConfiguration 给容器中导入一堆组件,组件都是从 xxxProperties 中提取属性值
  5. xxxProperties 又是和配置文件进行了绑定

效果:

修改配置文件,修改底层参数

所有场景自动配置好直接使用

可以注入SpringBoot配置好的组件随时使用

SPI机制

  • Java中的SPI(Service Provider Interface)是一种软件设计模式,用于在应用程序中动态地发现和加载组件。SPI的思想是,定义一个接口或抽象类,然后通过在classpath中定义实现该接口的类来实现对组件的动态发现和加载。
  • SPI的主要目的是解决在应用程序中使用可插拔组件的问题。例如,一个应用程序可能需要使用不同的日志框架或数据库连接池,但是这些组件的选择可能取决于运行时的条件。通过使用SPI,应用程序可以在运行时发现并加载适当的组件,而无需在代码中硬编码这些组件的实现类。
  • 在Java中,SPI的实现方式是通过在META-INF/services目录下创建一个以服务接口全限定名为名字的文件,文件中包含实现该服务接口的类的全限定名。当应用程序启动时,Java的SPI机制会自动扫描classpath中的这些文件,并根据文件中指定的类名来加载实现类。
  • 通过使用SPI,应用程序可以实现更灵活、可扩展的架构,同时也可以避免硬编码依赖关系和增加代码的可维护性。

在SpringBoot中也有SPI,META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

在resource文件中创建META-INF/spring目录并创建org.springframework.boot.autoconfigure.AutoConfiguration.imports

在文件中写入需要被自动加载全类名=>Java的SPI机制会自动扫描classpath中的这些文件,并根据文件中指定的类名来加载实现类。

com.chs.robot.starter.RobotAutoConfiguration

功能开关

  • 自动配置:全部都配置好,什么都不用管。 自动批量导入

    • 项目一启动,spi文件中指定的所有都加载。
  • @EnableXxxx:手动控制哪些功能的开启; 手动导入

    • 开启xxx功能
    • 都是利用 @Import 把此功能要用的组件导入进去

SpringBoot完整项目启动流程

生命周期启动加载流程

image-20240910104925143

自定义自动配置类(starter)

  • 场景:抽取聊天机器人场景,它可以打招呼
  • 效果:任何项目导入此starter都具有打招呼功能,并且问候语中的人名需要可以在配置文件中修改

创建自定义starter项目,引入spring-boot-starter基础依赖,删除自己的启动类

<!--
自定义starter ==>项目名称:robot-spring-boot-starter
1、所有starter都需要依赖这个基础starter;spring-boot-starter
2、把所有要抽取的业务组件都抽取到这个starter中;
 -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter</artifactId>
</dependency>

编写模块功能引入模块所有需要的依赖

//Controller模块
@RestController
public class RobotController {
    @Autowired
    RobotService robotService;
    @GetMapping("/robot/hello")
    public String sayHello(){
        String msg = robotService.sayHello();
        return msg;
    }
}
//Service模块
@Service
public class RobotServiceImpl implements RobotService {
    @Autowired
    RobotProperties robotProperties;
    @Override
    public String sayHello() {
        return "我是机器人【"+robotProperties.getName()+"】,使用底层大模型:【"+robotProperties.getModel()+"】;我们开始聊天吧";
    }
}

//配置类模块
@Component
@ConfigurationProperties(prefix = "robot")
@Data
public class RobotProperties {
    private String name;
    private String model;
}

编写xxxAutoConfiguration自动配置类,帮其他项目导入这个模块需要的所有组件

@EnableConfigurationProperties(RobotProperties.class) //开启自动配置类和配置文件的绑定,并把配置类放到容器中
@Configuration //把这个场景要用的所有组件导入到容器中
public class RobotAutoConfiguration {
    @Bean
    public RobotController robotController() {
        return new RobotController();
    }
    @Bean
    public RobotService  robotService() {
        return new RobotServiceImpl();
    }
}

第一层抽取:编写一个自动配置类,别人导入我的starter,

无需关心需要给容器中导入哪些组件,只需要导入自动配置类,

自动配置类帮你给容器中导入所有这个场景要用的组件

@import(RobotAutoConfiguration.class)

第二层抽取:只需要标注功能开关注解。@EnableRobot

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(RobotAutoConfiguration.class) //导入bean
public @interface EnableRobot {
}

第三层抽取:只需要导入starter,所有功能就绪

resources\META-INF\spring\中创建org.springframework.boot.autoconfigure.AutoConfiguration.imports

写入:自动配置类的全类名:指定的启动要加载的自动配置类

com.chs.robot.starter.RobotAutoConfiguration

使用:导依赖就能直接使用

<dependency>
	<groupId>com.chs</groupId>
	<artifactId>robot-spring-boot-starter</artifactId>
 	<version>0.0.1-SNAPSHOT</version>
</dependency>

通过配置文件修改传递的参数

robot.name="坤坤AI"
robot.model=chatgpt250
posted @   CH_song  阅读(8)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
点击右上角即可分享
微信分享提示