环境&工具 | 版本(or later) |
SpringBoot | 3.0.5+ |
IDEA | 2021.2.1+ |
Java | 17+ |
Maven | 3.5+ |
Tomcat | 10.0+ |
Servlet | 5.0+ |
GraalVM Community | 22.3+ |
Native Build Tools | 0.9.19+ |
SpringBoot 帮我们简单、快速地创建一个独立的、生产级别的 Spring 应用(说明:SpringBoot底层是Spring)。大多数 SpringBoot 应用只需要编写少量配置即可快速整合 Spring 平台以及第三方技术
特性:
● 快速创建独立Spring应用
说明:SpringBoot之前整合SSM:导包、写配置、启动运行
● 直接嵌入Tomcat、Jetty or Undertow(无需部署 war 包)【Servlet容器】
说明:SpringBoot之前开发一个Web应用,并部署,需要linux服务器,并在上面安装Java环境、Tomcat以及Mysql,将项目打成war包放到Tomcat的webapps下。有了SpringBoot,整个应用会打成jar包,只要目标服务器有Java环境,只需要将应用打成jar包,放到服务器中,执行java -jar命令就可以启动应用
● 提供可选的starter(重点),简化应用整合,场景启动器(starter)用途:web、json、邮件、oss(对象存储)、异步、定时任务、缓存...
说明:SpringBoot之前开发,需要导一堆包,控制好版本防止冲突。有了SpringBoot之后,会把web、json、邮件、oss(对象存储)、异步、定时任务、缓存... 这些开发常见的功能当作场景,为每一种场景准备了一个依赖,如:web-starter,mybatis-starter
● 按需自动配置Spring以及第三方库(重点)
说明:如果这些场景我们要使用(即生效),这个场景的所有配置都会自动配置好,自动配置也依赖一个原则叫约定大于配置。约定大于配置:每个场景都有很多默认配置。如果要自定义配置,在配置文件中修改就可以。
● 提供生产级特性:如监控指标、健康检查、外部化配置等
● 无代码生成、无xml
总结:简化开发,简化配置,简化整合,简化部署,简化监控,简化运维。
2.1.1 创建maven项目
4.0.0 com.atCarl.boot boot3-01-demo1.0-SNAPSHOT org.springframework.boot spring-boot-starter-parent3.0.8
2.1.2. 导入场景
org.springframework.boot spring-boot-starter-web
2.1.3. 编写代码
主程序
/** * 启动SpringBoot项目的主入口程序 */ @SpringBootApplication //这是一个SpringBoot应用 public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class,args); } }
业务
@RestController public class TestController { @GetMapping("/") public String test(){ return "hello SpringBoot"; } }
2.1.4. 运行示例
. ____ _ __ _ _ /\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v3.0.8) ....... . . . ....... . . . (log output here) ....... . . . ........ Started MyApplication in 1.553 seconds (process running for 2.281)
如果你打开一个 web 浏览器,访问localhost: 8080,你会看到下面的输出:
hello SpringBoot
2.1.5. 创建一个可执行的Jar包(打包)
在pom文件中添加spring-boot-maven-plugin
org.springframework.boot spring-boot-maven-plugin
mvn clean package 把项目打成可执行jar包
[INFO] Scanning for projects... [INFO] [INFO] -----------------------< com.carl:boot3-01-demo >----------------------- [INFO] Building boot3-01-demo 1.0-SNAPSHOT [INFO] from pom.xml [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- resources:3.3.1:resources (default-resources) @ boot3-01-demo --- [INFO] Copying 0 resource from src\main\resources to target\classes [INFO] Copying 0 resource from src\main\resources to target\classes [INFO] [INFO] --- compiler:3.10.1:compile (default-compile) @ boot3-01-demo --- [INFO] Nothing to compile - all classes are up to date [INFO] [INFO] --- resources:3.3.1:testResources (default-testResources) @ boot3-01-demo --- [INFO] skip non existing resourceDirectory D:\aboutProgramming\cgc-study\FrameWork\SpringBoot3\boot3-01-demo\src\test\resources [INFO] [INFO] --- compiler:3.10.1:testCompile (default-testCompile) @ boot3-01-demo --- [INFO] Nothing to compile - all classes are up to date [INFO] [INFO] --- surefire:2.22.2:test (default-test) @ boot3-01-demo --- [INFO] [INFO] --- jar:3.3.0:jar (default-jar) @ boot3-01-demo --- [INFO] [INFO] --- spring-boot:3.0.8:repackage (repackage) @ boot3-01-demo --- [INFO] Replacing main artifact with repackaged archive [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 2.689 s [INFO] Finished at: 2023-07-11T22:41:19+08:00 [INFO] ------------------------------------------------------------------------ Process finished with exit code 0
在target目录中会看到boot3-01-demo-1.0-SNAPSHOT.jar
使用java -jar命令,运行该应用程序:java -jar boot3-01-demo-1.0-SNAPSHOT.jar 启动项目
外部化配置
在boot3-01-demo-1.0-SNAPSHOT.jar的同级目录下放一个application.properties,可以通过它来改配置,例如改端口号
执行java -jar boot3-01-demo-1.0-SNAPSHOT.jar 启动项目,会看到端口号已经改变
D:\app>java -jar boot3-01-demo-1.0-SNAPSHOT.jar . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v3.0.8) 2023-07-11T22:57:32.515+08:00 INFO 27904 --- [ main] com.atCarl.boot.MyApplication : Starting MyApplication v1.0-SNAPSHOT using Java 17.0.7 with PID 27904 (D:\app\boot3-01-demo-1.0-SNAPSHOT.jar started by cgc'lenovo in D:\app) 2023-07-11T22:57:32.519+08:00 INFO 27904 --- [ main] com.atCarl.boot.MyApplication : No active profile set, falling back to 1 default profile: "default" 2023-07-11T22:57:33.762+08:00 INFO 27904 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8091 (http) 2023-07-11T22:57:33.774+08:00 INFO 27904 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2023-07-11T22:57:33.775+08:00 INFO 27904 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.10] 2023-07-11T22:57:33.880+08:00 INFO 27904 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2023-07-11T22:57:33.882+08:00 INFO 27904 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1284 ms 2023-07-11T22:57:34.303+08:00 INFO 27904 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8091 (http) with context path '' 2023-07-11T22:57:34.321+08:00 INFO 27904 --- [ main] com.atCarl.boot.MyApplication : Started MyApplication in 2.29 seconds (process running for 2.722)
2.2.1. 简化整合
导入相关的场景,拥有相关的功能。这些相关的场景称之为场景启动器
SpringBoot默认支持的所有场景:Developing with Spring Boot
2.2.2. 简化开发
无需编写任何配置,直接开发业务
2.2.3. 简化配置
application.properties:
2.2.4. 简化部署
打包为可执行的jar包,Linux服务器上有java环境,就可以通过java -jar 命令来运行。
2.2.5. 简化运维
修改配置(外部放一个application.properties文件)、监控、健康检查。
一键创建好整个项目结构
项目结构
上面通过一个demo,展现了SpringBoot应用的便捷,所有的这些便捷特性都源于SpringBoot底层的两个机制:依赖管理机制、自动配置机制
首先我们来思考几个问题:
1) 为什么导入starter-web,所有相关依赖都导入进来?
根据maven依赖传递原则:A依赖B,B依赖C, A就拥有B和C,只要导入场景启动器, 场景启动器会自动把这个场景相关的所有核心依赖全部导入进来。因此,想要开发什么场景,就导入什么场景启动器。
2) 为什么版本号都不用写?Dependency Management
每个SpringBoot项目都有一个父项目spring-boot-starter-parent,而spring-boot-starter-parent的父项目是spring-boot-dependencies,spring-boot-dependencies中有
父项目可以看做是版本仲裁中心,把所有常见的jar包的依赖版本都声明好了。所以,我们只需要添加依赖,不需要管版本。比如:mysql-connector-j
3) 自定义版本号
利用maven的就近原则(如果在当前项目中声明了版本,就用当前项目的)。做法:在当前项目properties标签中声明父项目用的版本属性的key或者直接在导入依赖的时候声明版本。
4) 第三方的jar包
SpringBoot父项目没有管理的需要自行声明好,例如,SpringBoot父项目没有管理druid相关的jar包,需要在pom文件中自行声明。
com.alibaba druid1.2.16
3.2.1. 初步理解
● 自动配置的 Tomcat、SpringMVC 等
导入场景,容器中就会自动配置好这个场景的核心组件。
以前:需要配置DispatcherServlet、ViewResolver、CharacterEncodingFilter...
现在:有了SpringBoot,它会帮我们自动配置好这些组件
如何验证?
/** * SpringBoot主程序 */ @SpringBootApplication public class Boot302DemoApplication { public static void main(String[] args) { //java10:局部变量类型的自动推断 var applicationContext = SpringApplication.run(Boot302DemoApplication.class, args); //获取容器中所有组件的名字 String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames(); // dispatcherServlet、viewResolver、characterEncodingFilter、multipartResolver // SpringBoot把以前配置的核心组件现在都给我们自动配置好了。 for (String beanDefinitionName : beanDefinitionNames) { System.out.println(beanDefinitionName); } } }
容器中有了什么组件,就具有什么功能
● 默认的包扫描规则
@SpringBootApplication 标注的类就是主程序类,SpringBoot只会扫描主程序所在的包及其下面的子包,相当于自动的component-scan功能。按照默认的包扫描规则,如果组件没有声明在SpringBoot主程序所在的包及其下面的子包,组件是不生效的。
● 自定义扫描路径
@SpringBootApplication(scanBasePackages = "com.atcarl")
@ComponentScan("com.atcarl") 直接指定扫描的路径
● 配置默认值
配置文件中的所有配置,最终都会保存到某一个对象里面的相应属性中,也就是配置文件的所有配置项都是和某个类的属性值进行一一绑定的。绑定了配置文件中每一项值的类: 属性类。比如:
ServerProperties绑定了所有Tomcat服务器有关的配置
MultipartProperties绑定了所有文件上传相关的配置
...
参照官方文档,或者参照绑定的属性类。
● 按需加载自动配置
举例,为什么我们导入spring-boot-starter-web这个场景,Tomcat、SpringMVC等相关就都会配置好?
分析:导入场景spring-boot-starter-web,场景启动器除了会导入相关功能依赖,还会导入一个spring-boot-starter,是所有starter的starter,基础核心starter。在spring-boot-starter中又导入了一个包spring-boot-autoconfigure,包里面都是各种场景的AutoConfiguration(自动配置类)。虽然全场景的自动配置都在spring-boot-autoconfigure这个包,但不是全都开启的,导入哪个场景就开启哪个自动配置。
总结: 导入场景启动器,触发 spring-boot-autoconfigure这个包的自动配置生效,容器中就会具有相关场景的功能。
3.2.2. 完整流程
下面来思考几个问题:
1)SpringBoot怎么实现导入一个starter、写一些简单配置,应用就能跑起来,我们无需关心整合?
2)为什么Tomcat的端口号可以配置在application.properties中,并且Tomcat能启动成功?
3)导入场景后哪些自动配置能生效?
以spring-boot-starter-web为例,自动配置流程细节梳理:
1)导入spring-boot-starter-web:导入了web开发场景
① web开发场景启动器导入了相关场景的所有依赖:starter-json、starter-tomcat、spring-web、spring-webmvc。
② 每个场景启动器都引入了一个spring-boot-starter,核心场景启动器。
③ 核心场景启动器引入了spring-boot-autoconfigure包,spring-boot-autoconfigure包里面囊括了SpringBoot官方提供的所有场景的所有配置。也就是说,所有场景要用的组件,SpringBoot在这些配置类里面都写好了。
④ 只要spring-boot-autoconfigure这个包下的所有配置类都能生效,那么相当于SpringBoot官方写好的整合功能就生效了。
⑤ 问题在于虽然spring-boot-autoconfigure包下有许多配置类,可以给容器提供很多组件,但SpringBoot默认是扫描不到 spring-boot-autoconfigure包下写好的配置类(说明:这些配置类给我们做了整合操作,原来需要我们自己做的一些整合工作,现在被SpringBoot官方封装到这些配置类里了)。
⑥ SpringBoot默认只扫描主程序所在的包及其下面的子包。导了spring-boot-autoconfigure包,只是把spring-boot-autoconfigure包里面的所有配置类,引到了项目中,并没有真正生效。所以,只需要让这些配置被扫描到并且生效了,相应的功能就有了。怎么让这些配置生效?接下来我们就要看第2步,分析主程序了
2)写一个主程序,在主程序标注@SpringBootApplication
① @SpringBootApplication由三个注解组成:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan。
② 这3个注解就形成了SpringBoot的默认行为,SpringBoot默认只能扫描自己主程序所在的包及其下面的子包,扫描不到 spring-boot-autoconfigure包中官方写好的配置类。
③ @EnableAutoConfiguration,是SpringBoot 开启自动配置的核心。有了这个注解,SpringBoot会让spring-boot-autoconfigure包下写好的配置类生效,具体是怎么生效的呢?请看下面分析
● @EnableAutoConfiguration是由@Import(AutoConfigurationImportSelector.class)提供功能:批量给容器中导入组件。通过AutoConfigurationImportSelector,将类导入进来,因为AutoConfigurationImportSelector实现了DeferredImportSelector
● SpringBoot启动默认会加载142个配置类。这142个配置类来自于spring-boot-autoconfigure下META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件指定
● 项目启动的时候利用 @Import 批量导入组件机制,把spring-boot-autoconfigure包下META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中指定的142个xxxxAutoConfiguration类(自动配置类)导入进来。虽然导入了142个自动配置类,但是这142个配置类不一定都生效。
● 按需生效,并不是这142个自动配置类都能生效,每一个自动配置类都有条件注解@ConditionalOnxxx,只有条件成立,才能生效。
总结:@EnableAutoConfiguration最终的作用就是让SpringBoot在启动的时候,利用@Import批量导入机制,把spring-boot-autoconfigure包下META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件指定的所有类全部导进来。
3)xxxxAutoConfiguration自动配置类
① xxxxAutoConfiguration自动配置类使用@Bean,向容器中放许多组件,这些组件就能工作了。
② 每个自动配置类都可能有这个注解:@EnableConfigurationProperties,用来把配置文件中配的指定前缀的属性值封装到 xxxProperties属性类中。
③ 以Tomcat为例:服务器的所有配置都是以server开头的。配置都封装到了ServerProperties属性类中。
④ xxxxAutoConfiguration给容器中放的所有组件的一些核心参数,都来自于xxxProperties,xxxProperties都是和配置文件绑定。只需要改配置文件的值,核心组件的底层参数都能修改。
4)写业务,全程无需关心各种整合(底层把这些整合写好了,而且也生效了)。
核心流程总结:
1)导入starter,就会导入autoconfigure包。
2)autoconfigure 包里面有一个文件 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,里面指定了所有启动要加载的自动配置类。
3)@EnableAutoConfiguration 会自动的把META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件里面写的所有自动配置类都导入进来。xxxAutoConfiguration 是有条件注解进行按需加载。
4)加载进来的xxxAutoConfiguration会给容器中导入许多组件,即这个场景要整合什么,相应的组件就会放到容器中,组件都是从 xxxProperties中提取属性值。
5)xxxProperties又是和配置文件进行了绑定。
联动过程:导入starter之后,底层会自动导入autoconfigure.jar包,该包指定的140+个自动配置类(xxxAutoConfiguration)就自动进来了,但进来之后不代表可以直接使用,还需要指定场景的starter才可以激活,这也就是按需加载的意思。看源码,每个xxxAutoConfiguration会给Spring容器放许多的组件(通过@Bean注解放入ioc容器),组件的属性被xxxProperties类进行提取,并于application.properties配置文件实现绑定,而程序员在定制化的时候,就是通过修改配置文件,实现定制化。修改配置文件之后,组件中的属性也就相应被修改了,在xxxAutoConfiguration源码层面上看,修改内容就会被加载。
最终实现的效果:导入starter,修改配置文件,就能修改底层行为。
3.2.3. 如何学好SpringBoot
SpringBoot其实相当于框架的框架,帮我们做所有框架的整合,底层基于Spring,学好SpringBoot要做到能调整每一个场景的底层行为。随着项目规模的增大,光靠SpringBoot提供的自动配置是不够的,一定会用到底层自定义。
1)理解自动配置原理
导入starter --> 生效xxxxAutoConfiguration -->xxxxAutoConfiguration 一定会向容器中注册许多组件 --> 组件里面的一些核心参数一般都会绑定在xxxProperties属性类中 --> xxxProperties属性类有和配置文件关联
2)理解其它框架底层
3)可以随时定制化任何组件
定制化修改有两种方式:① 修改配置文件 ② 自定义组件
普通开发:导入starter,Controller、Service、Mapper、偶尔修改配置文件
高级开发:自定义组件、自定义配置、自定义starter
无论整合任何场景,我们需要关注的核心:
● 这个场景的自动配置导入了哪些组件,我们能不能自动装配(Autowired)进来使用
● 能不能通过修改配置,改变组件的一些默认参数。
● 需不需要自己完全定义这个组件
● 场景定制化
最佳实战:
1)选场景,导入到项目,场景来源:
官方:starter
第三方:去maven仓库搜,找到相关的starter
2)写配置,改配置文件关键项
以数据库为例,需要配数据库参数,如连接地址、账号密码...
3)分析这个场景给我们导入了哪些能用的组件
我们可以自动装配这些组件进行后续使用。如果不满意SpringBoot提供的自动配好的默认组件,就要定制化
定制化:
● 改配置
● 自定义组件
整合redis:
1)选场景:spring-boot-starter-data-redis
RedisAutoConfiguration 就是这个场景的自动配置类
2)写配置:
通过@EnableConfigurationProperties(RedisProperties.class),分析到这个场景的自动配置类开启了哪些属性绑定关系,修改redis相关的配置
3)分析组件:
分析到 RedisAutoConfiguration 给容器中放了 StringRedisTemplate。给业务代码中自动装配 StringRedisTemplate
4)定制化
修改配置文件
自定义组件,自己给容器中放一个 StringRedisTemplate
SpringBoot摒弃XML配置方式,改为全注解驱动
4.1.1. 组件注册
@Configuration、@SpringBootConfiguration
@Configuration和@SpringBootConfiguration都是加在类上,用来说明这是一个配置类。@Configuration 代表这是一个普通配置类,而@SpringBootConfiguration 代表这是一个SpringBoot配置类,@SpringBootConfiguration的指向更明确,但是这两个注解都可以将一个普通类标识为一个配置类。
/** * 这是一个配置类,替代以前的配置文件,配置类本身也是容器中的组件 * * @Configuration 代表这是一个普通配置类 * @SpringBootConfiguration 代表这是一个SpringBoot配置类 * * 也可以使用@Import将类导入到容器中作为一个组件(例如:@Import(Cat.class)),而且@Import也是要写到配置类上的 * @Import 给容器中放指定类型的组件,组件的名称默认是全类名 */ //@Configuration @SpringBootConfiguration @Import(Cat.class) public class AppConfig { /** * 替代以前的bean标签,组件在容器中的名字默认是方法名 * 可以修改方法名或者注解的值(@Bean("user1"))来修改组件在容器中的名字 * * 组件默认是单例的 */ @Scope(scopeName = "prototype") @Bean public User user() { var user = new User(); user.setId(1); user.setName("zhangsan"); return user; } /** * 将第三方包中的任意类的对象注册成为容器中的组件 * 也可以使用@Import(TransactionTimeoutException.class)进行导入 */ @Bean public TransactionTimeoutException transactionTimeoutException(){ return new TransactionTimeoutException(); } }
@Bean、@Scope
@Controller、 @Service、@Repository、@Component
@Import
@ComponentScan
步骤:
1)使用@Configuration编写一个配置类
2)在配置类中,自定义方法给容器中注册组件,方法上要加@Bean
3)或使用@Import 导入第三方的组件
4.1.2. 条件注解
如果注解指定的条件成立,则触发指定行为
@ConditionalOnXxx
@ConditionalOnClass:如果类路径中存在这个类,则触发指定行为
@ConditionalOnMissingClass:如果类路径中不存在这个类,则触发指定行为
@ConditionalOnBean:如果容器中存在这个Bean(组件),则触发指定行为
@ConditionalOnMissingBean:如果容器中不存在这个Bean(组件),则触发指定行为。@ConditionalOnMissingBean作用在@Bean定义上,也就是说在容器加载它作用的Bean时,检查容器中是否存在目标类型(@ConditionalOnMissingBean注解的value值)的Bean了,如果存在则跳过Bean的默认加载动作,如果不存在则加载该Bean完成注册。
场景:
1)如果系统存在FastsqlException这个类,给容器中放一个Cat组件,名cat01,否则,就给容器中放一个Dog组件,名dog01
2)如果容器中有Dog这个组件,就给容器中放一个 User组件,名为zhangsan 否则,就放一个User,名叫lisi
/** * @ConditionalOnXxx * 放在类级别,如果注解判断生效,则整个配置类才生效 * 放在方法级别,单独对这个方法进行注解判断 */ //@ConditionalOnMissingClass("com.alibaba.druid.FastsqlException")//放在类级别 @SpringBootConfiguration public class AppConfig2 { /** * 给容器中放一个名为cat01的组件,但要求是:只有系统中存在FastsqlException这个类才将Cat类型的bean添加到容器中 */ @ConditionalOnClass(name = "com.alibaba.druid.FastsqlException")//放在方法级别 @Bean public Cat cat01(){ return new Cat(); } @ConditionalOnMissingClass("com.alibaba.druid.FastsqlException") @Bean public Dog dog01(){ return new Dog(); } @ConditionalOnBean(Dog.class) @Bean public User zhangsan(){ return new User(); } @ConditionalOnMissingBean(Dog.class) @Bean public User lisi(){ return new User(); }
因为pom.xml中没有引druid的包,所以类路径中不存在,因此,输出结果为:
dog:dog01 user:zhangsan
4.1.3. 属性绑定
@ConfigurationProperties注解的作用: 声明组件的属性和配置文件哪些前缀开始的项进行绑定,通常用来将properties或yaml配置文件中的内容读取并封装到JavaBean中,可以声明在类和方法上。
@EnableConfigurationProperties注解的作用:让使用了@ConfigurationProperties 注解的类生效,并且将该类注入到IOC容器中,交由IOC容器进行管理。@EnableConfigurationProperties主要用于导入第三方写好的组件,进行属性绑定。
将容器中任意组件(Bean)的属性值和配置文件的配置项的值进行绑定
1)给容器中注册组件(@Component、@Bean),组件必须要注册到容器中
2)使用@ConfigurationProperties 声明组件和配置文件的哪些配置项进行绑定
使用@ConfigurationProperties + @Component 实现配置绑定
@ConfigurationProperties(prefix = "pig") @Component public class Pig { private Long id; private String name; private Integer age; public Long getId() { return id; } public void setId(Long id) { this.id = id; } 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 "Pig{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; } }
@ConfigurationProperties + @Bean 实现配置绑定
@SpringBootConfiguration public class AppConfig1 { @ConfigurationProperties(prefix = "pig")//prefix表示配置文件属性的前缀 @Bean public Pig pig(){ return new Pig(); } }
从输出结果中,可以看到出现了中文乱码。
pig:Pig{id=1, name='??', age=5}
在配置文件中写中文值,需要在idea中进行设置,解决中文乱码
从输出结果中看到乱码问题已解决。
pig:Pig{id=1, name='佩奇', age=5}
@EnableConfigurationProperties + @ConfigurationProperties 实现配置绑定
@ConfigurationProperties(prefix = "pig") //@Component public class Pig { private Long id; private String name; private Integer age; public Long getId() { return id; } public void setId(Long id) { this.id = id; } 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 "Pig{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; } }
/** * * @EnableConfigurationProperties作用: * 1.开启Pig组件的属性绑定 * 2.默认会把Pig这个组件放到容器中,不需要单独注册 */ @EnableConfigurationProperties(Pig.class) //主要用于导入第三方写好的组件,进行属性绑定 @SpringBootConfiguration public class AppConfig1 { }
测试效果和上面一样。
问题:从上面示例中我们可以看到,在属性绑定中,@EnableConfigurationProperties和@Component的效果一样。既然我们可以通过@Component、@Bean将组件注册到容器中,那么为什么还要有@EnableConfigurationProperties?
SpringBoot默认只扫描自己主程序所在的包。如果导入第三方包,即使第三方包里面的组件上标注了 @Component、@ConfigurationProperties 注解,也不会生效,因为SpringBoo连这些组件都扫描不进来。此时使用@EnableConfigurationProperties这个注解就可以快速把组件注册进容器并完成属性绑定,但是组件上仍需加@ConfigurationProperties,来声明组件的属性和配置文件的哪些配置项进行绑定。
总结:属性绑定,首先,需要将组件在容器中进行注册。如果是我们自己写的组件,可以通过@Component、@Bean等方式,将组件注册到容器中。如果是第三方包里的组件,可以通过@EnableConfigurationProperties将其注册到容器中,并进行属性绑定。其次,无论是我们自己写的还是第三方包里的组件,都需要使用@ConfigurationProperties 来声明组件和配置文件的哪些配置项进行绑定。
优点:SpringBoot 集中化管理配置,application.properties
问题:配置多以后难阅读和修改,层级结构辨识度不高
YAML 是 "YAML Ain't a Markup Language"(YAML 不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:"Yet Another Markup Language"(是另一种标记语言)。
● 设计目标,就是方便人类读写
● 层次分明,更适合做配置文件
● 使用.yaml或 .yml作为文件后缀
4.2.1. 基本语法
支持的写法:
4.2.2. 示例
@Data //自动生成JavaBean属性的getter/setter @NoArgsConstructor //自动生成无参构造器 @AllArgsConstructor //自动生成全参构造器 @Component @ConfigurationProperties(prefix = "person") //和配置文件person前缀的所有配置进行绑定 public class Person { private String name; private Integer age; private Date birthDay; private Boolean like; private Child child; //嵌套对象 private Listdogs; //数组(里面是对象) private Map cats; //表示Map } @Data public class Dog { private String name; private Integer age; } @Data public class Child { private String name; private Integer age; private Date birthDay; private List text; //数组 } @Data public class Cat { private String name; private Integer age; }
复杂对象表示-使用Properties文件
复杂对象表示-使用yaml文件
4.2.3. 细节
● birthDay 推荐写为 birth-day
● 文本
单引号不会转义【\n 则为普通字符串显示】
双引号会转义【\n会显示为换行符】
● 大文本
| 开头,大文本写在下层,保留文本格式,换行符正确显示
> 开头,大文本写在下层,折叠换行符,把换行符变成一个空格
● 多文档合并
使用---可以把多个yaml文档合并在一个文档中,每个文档区依然认为内容独立
4.2.4. 小技巧:lombok
简化JavaBean 开发,自动生成构造器、getter/setter、自动生成Builder模式等。使用@Data等注解
org.projectlombok lombokcompile
规范:项目开发不要编写System.out.println(),应该用日志记录信息
日志门面就是在日志框架和应用程序之间架设一个沟通的桥梁,对于应用程序来说,无论底层的日志框架如何变,都不需要有任何感知
日志门面可以理解为java中的一个interface(接口),而日志框架就是实现类
4.3.1.简介
● SpringBoot支持 jul,log4j2,logback,logback是默认使用的。
● SpringBoot 提供了默认的控制台输出配置,也可以配置输出为文件。
● 虽然日志框架很多,但是我们不用担心,使用SpringBoot的默认配置就能工作的很好。
● SpringBoot是基于Spring的,Spring使用commons-logging作为内部日志,但底层日志实现是开放的,可对接其他日志框架。
说明:Spring以前用commons-logging作为日志,还要导入commons-logging这个日志包。但在Spring5及以后,直接把commons-logging整合到Spring内部了,不需要导入commons-logging这个日志包,commons-logging被Spring直接自己写了。
SpringBoot是怎么把日志默认配置好的?
1)每个starter场景,都会导入一个核心场景spring-boot-starter。
2)核心场景spring-boot-starter中又导入了spring-boot-starter-logging,核心场景就引入了日志的所有功能。
3)Springboot默认的日志框架:使用了slf4j + logback 组合作为默认底层日志 ,slf4j作为日志门面,而logback作为日志框架的实现。
4)日志是项目一启动就要用的,而xxxAutoConfiguration是项目启动过程中会给容器中注册一些组件,是后来用的。所以,SpringBoot在自动配日志的时候,并不是利用xxxAutoConfiguration配置原理,而是利用监听器机制配置好的,ApplicationListener。
6)SpringBoot利用监听器,在项目启动的时候帮我们配好日志。日志配置好后,日志所有的配置都可以通过修改配置文件实现,以logging开始的所有配置,都是和日志有关的配置(日志配置没有利用xxxAutoConfiguration配置原理,所以没有xxxProperties属性类来绑定配置文件)。
4.3.2. 日志格式
2023-07-26T21:45:42.315+08:00 INFO 512 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' 2023-07-26T21:45:42.328+08:00 INFO 512 --- [ main] c.a.logging.Boot03LoggingApplication : Started Boot03LoggingApplication in 4.239 seconds (process running for 5.598)
默认输出格式:
注意: logback 没有FATAL级别,对应的是ERROR
控制台默认输出格式值:%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd'T'HH:mm:ss.SSSXXX}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}
控制台格式可修改为:'%d{yyyy-MM-dd HH:mm:ss} %-5level [%thread] %logger{15} ===> %msg%n'
默认值:参照spring-boot包中META-INF下面的additional-spring-configuration-metadata.json文件
4.3.3. 记录日志
@RestController public class TestLogController { //从日志工厂中获取记录当前类的日志记录器 Logger logger = LoggerFactory.getLogger(getClass()); @GetMapping("/log") public String testLog(){ logger.info("method:testLog is invoked"); return "hello"; } }
或者使用Lombok的@Slf4j注解
@Slf4j //导了lombok,也可以用lombok中的注解:@Slf4j @RestController public class TestLogController { @GetMapping("/log") public String testLog(){ //加了@Slf4j,在底层就会帮我们自动在TestLogController这个类里面注入一个属性:log log.info("method:testLog is invoked"); return "hello"; } }
4.3.4. 日志级别
日志级别的作用:将系统里所有用log记录的日志划分等级
● 由低到高:ALL,TRACE,DEBUG,INFO,WARN,ERROR,FATAL,OFF
只会打印指定级别及以上级别的日志
ALL:打印所有日志
TRACE:追踪框架详细流程日志,一般不使用
DEBUG:开发调试细节日志
INFO:关键、感兴趣信息日志
WARN:警告但不是错误的信息日志,比如:版本过时
ERROR:业务错误日志,比如出现各种异常
FATAL:致命错误日志,比如jvm系统崩溃
OFF:关闭所有日志记录
● 不指定日志级别的所有类,都使用root指定的级别作为默认级别
● SpringBoot日志默认级别是 INFO
在application.properties/yaml中配置logging.level.
level可取值范围:TRACE、 DEBUG、INFO、WARN、ERROR、FATAL or OFF,定义在 LogLevel类中
root 的logger-name叫root,可以配置logging.level.root=warn,代表所有未指定日志级别都使用 root 的 warn 级别
4.3.5. 日志分组
比较有用的技巧是:将相关的logger分组在一起,统一配置。SpringBoot 也支持,比如:Tomcat相关的日志统一设置
logging.group.tomcat=org.apache.catalina,org.apache.coyote,org.apache.tomcat logging.level.tomcat=trace
SpringBoot 预定义两个组
Name(组名) | Loggers(对哪些包进行了分组) |
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 |
也就是说改了相应组的日志级别,这个组对应的所有包的日志级别都会改变。例如:logging.level.sql=debug,那org.springframework.jdbc.core, org.hibernate.SQL, org.jooq.tools.LoggerListener这几个包的日志级别都会改为debug。
4.3.6. 文件输出
SpringBoot 默认只把日志写在控制台,如果想额外记录到文件,可以在application.properties中添加logging.file.name 或者 logging.file.path配置项。
logging.file.name | logging.file.path | 示例 | 效果 |
未指定 | 未指定 | 仅控制台输出 | |
指定 | 未指定 | my.log | 写入指定文件。文件前可以加路径 |
未指定 | 指定 | /var/log | 写入到指定目录/var/log下,文件名为spring.log |
指定 | 指定 | 以logging.file.name为准 |
4.3.7. 文件归档与滚动切割
归档:把每天的日志单独存到一个文档中。
切割:如,每个文件限制最大10MB,超过大小切割成另外一个文件。
1)每天的日志应该独立分割出来存档。如果使用logback(SpringBoot 默认整合),可以通过application.properties/yaml文件指定日志滚动规则。支持的滚动规则设置如下:
配置项 | 描述 |
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). |
2)如果是其他日志框架,需要自行配置(在项目中添加log4j2.xml或log4j2-spring.xml,能自动识别,但前提名字必须是log4j2.xml或log4j2-spring.xml)
4.3.8. 自定义配置
通常我们配置 application.properties 就够了,当然也可以自定义,SpringBoot是支持的,但也是要按照Spring的命名规范,比如:
日志系统 | 自定义 |
Logback | logback-spring.xml,logback-spring.groovy,logback.xml or logback.groovy |
Log4j2 | log4j2-spring.xml or log4j2.xml |
JDK (Java Util Logging) | logging.properties |
如果可能,我们建议您在日志配置文件名中使用-spring 变量(例如,logback-spring.xml 而不是 logback.xml)。如果您使用标准配置文件,Spring无法完全控制日志初始化。
4.3.9. 切换日志组合
org.springframework.boot spring-boot-starter-weborg.springframework.boot spring-boot-starterorg.springframework.boot spring-boot-starter-loggingorg.springframework.boot spring-boot-starter-log4j2
log4j2支持yaml和json格式的配置文件
格式 | 依赖 | 文件名 |
YAML | com.fasterxml.jackson.core:jackson-databind + com.fasterxml.jackson.dataformat:jackson-dataformat-yaml | log4j2.yaml + log4j2.yml |
JSON | com.fasterxml.jackson.core:jackson-databind | log4j2.json + log4j2.jsn |
4.3.10. 最佳实战
1)导入任何第三方框架,先排除它的日志包,因为SpringBoot底层控制好了日志。
2)修改 application.properties 配置文件,就可以调整日志的所有行为。如果不够,可以编写日志框架自己的配置文件放在类路径下就行,比如logback-spring.xml,log4j2-spring.xml。
3)如需对接专业日志系统,也只需要把 logback 记录的日志输到kafka之类的中间件,这和SpringBoot没关系,都是日志框架自己的配置,修改日志框架对应的配置文件即可。
4)业务中使用slf4j-api记录日志,不要再 sout 了。