1.创建第一个项目:
1.引入依赖:
1 2 3 4 5 6 <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 3.0.5</version > </parent >
这里可能会报错:解决Maven ‘parent.relativePath‘ of POM_BingTaiLi的博客-CSDN博客
2. 导入场景
场景启动器
1 2 3 4 5 6 7 8 <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > </dependencies >
3. 主程序
1 2 3 4 5 6 7 @SpringBootApplication //这是一个SpringBoot应用 public class MainApplication { public static void main(String[] args) { SpringApplication.run(MainApplication.class,args); } }
4. 业务
1 2 3 4 5 6 7 8 9 10 @RestController//将返回内容写入返回体中 public class HelloController { @GetMapping("/hello") public String hello(){ return "Hello,Spring Boot 3!"; } }
5. 测试
默认启动访问: localhost:8080/hello
6. 打包
1 2 3 4 5 6 7 8 9 10 <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > </plugin > </plugins > </build >
mvn clean package把项目打成可执行的jar包
java -jar demo.jar启动项目
在该路径下输入cmd,然后运行jar包(命令:jva -jar demo.jar,注意这里必须要字面-jar,因为运行的是jar包)
但是能够成功运行要求系统java的环境变量配置的jdk17以上。
3、应用分析
思考:
1、为什么导入starter-web所有相关依赖都导入进来?
1 2 3 4 5 6 7 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency >
maven依赖传递原则。A-B-C: A就拥有B和C
导入 场景启动器。 场景启动器 自动把这个场景的所有核心依赖全部导入进来
2、为什么版本号都不用写?
每个boot项目都有一个父项目spring-boot-starter-parent(它也是一个场景)
parent的父项目是spring-boot-dependencies(其中的标签中把所有常见的jar的依赖版本都声明好了。)
父项目 版本仲裁中心 ,把所有常见的jar的依赖版本都声明好了。
比如:mysql-connector-j
3、自定义版本号
利用maven的就近原则
直接在当前项目properties标签中声明父项目用的版本属性的key
直接在导入依赖的时候声明版本
4、第三方的jar包
1 2 3 4 5 6 7 <dependency > <groupId > com.alibaba</groupId > <artifactId > druid</artifactId > <version > 1.2.16</version > </dependency >
2. 自动配置机制
1. 初步理解
自动配置 的 Tomcat、SpringMVC 等
导入场景 ,容器中就会自动配置好这个场景的核心组件。
以前:DispatcherServlet、ViewResolver、CharacterEncodingFilter…
现在:自动配置好的这些组件
验证:容器中有了什么组件,就具有什么功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public static void main(String[] args) { //java10: 局部变量类型的自动推断(可以不用声明变量的类型,直接根据右边的表达式推断) var ioc = SpringApplication.run(MainApplication.class, args); //1、获取容器中所有组件的名字 String[] names = ioc.getBeanDefinitionNames(); //2、挨个遍历: // dispatcherServlet、beanNameViewResolver、characterEncodingFilter、multipartResolver // SpringBoot把以前配置的核心组件现在都给我们自动配置好了。 for (String name : names) { System.out.println(name); } }
默认的包扫描规则
@SpringBootApplication 标注的类就是主程序类
SpringBoot只会扫描主程序所在的包及其下面的子包,自动的component-scan功能
自定义扫描路径
@SpringBootApplication(scanBasePackages = “com.atguigu”)//scanBasePackages指定扫描的位置
@ComponentScan(“com.atguigu”) 直接指定扫描的路径
配置默认值
配置文件 的所有配置项是和某个类的对象 值进行一一绑定的。
绑定了配置文件中每一项值的类: 属性类 。
比如:
ServerProperties绑定了所有Tomcat服务器有关的配置
MultipartProperties绑定了所有文件上传相关的配置
…参照官方文档 :或者参照 绑定的 属性类 。
按需加载自动配置
导入场景spring-boot-starter-web
场景启动器除了会导入相关功能依赖(ctrl+鼠标右击可以查看导入的依赖),还导入一个spring-boot-starter(每个场景ctrl点进去的配置文件都有一个的spring-boot-starter),是所有starter的starter,这个starter基础核心starter
spring-boot-starter导入了一个包 spring-boot-autoconfigure。包里面都是各种场景的AutoConfiguration自动配置类
虽然全场景的自动配置都在 spring-boot-autoconfigure这个包,但是不是全都开启的。
总结: 导入场景启动器、触发 spring-boot-autoconfigure这个包的自动配置生效、容器中就会具有相关场景的功能
1. 常用注解
SpringBoot摒弃XML配置方式,改为全注解驱动
1. 组件注册
@Configuration 、@SpringBootConfiguration
@Bean 、@Scope
@Controller、 @Service、@Repository、@Component
@Import
@ComponentScan
步骤:
1、@Configuration 编写一个配置类
2、在配置类中,自定义方法给容器中注册组件。配合@Bean
3、或使用@Import 导入第三方的组件
2. 条件注解
如果注解指定的条件成立 ,则触发指定行为
@ConditionalOnXxx
@ConditionalOnClass:如果类路径中存在这个类,则触发指定行为
@ConditionalOnMissingClass:如果类路径中不存在这个类,则触发指定行为
@ConditionalOnBean:如果容器中存在这个Bean(组件),则触发指定行为
@ConditionalOnMissingBean:如果容器中不存在这个Bean(组件),则触发指定行为
场景:
如果存在FastsqlException这个类,给容器中放一个Cat组件,名cat01,
否则,就给容器中放一个Dog组件,名dog01
如果系统中有dog01这个组件,就给容器中放一个 User组件,名zhangsan
否则,就放一个User,名叫lisi
@ConditionalOnBean(value=组件类型,name=组件名字):判断容器中是否有这个类型的组件,并且名字是指定的值
@ConditionalOnRepositoryType (org.springframework.boot.autoconfigure.data)
@ConditionalOnDefaultWebSecurity (org.springframework.boot.autoconfigure.security)
@ConditionalOnSingleCandidate (org.springframework.boot.autoconfigure.condition)
@ConditionalOnWebApplication (org.springframework.boot.autoconfigure.condition)
@ConditionalOnWarDeployment (org.springframework.boot.autoconfigure.condition)
@ConditionalOnJndi (org.springframework.boot.autoconfigure.condition)
@ConditionalOnResource (org.springframework.boot.autoconfigure.condition)
@ConditionalOnExpression (org.springframework.boot.autoconfigure.condition)
@ConditionalOnClass (org.springframework.boot.autoconfigure.condition)
@ConditionalOnEnabledResourceChain (org.springframework.boot.autoconfigure.web )
@ConditionalOnMissingClass (org.springframework.boot.autoconfigure.condition)
@ConditionalOnNotWebApplication (org.springframework.boot.autoconfigure.condition)
@ConditionalOnProperty (org.springframework.boot.autoconfigure.condition)
@ConditionalOnCloudPlatform (org.springframework.boot.autoconfigure.condition)
@ConditionalOnBean (org.springframework.boot.autoconfigure.condition)
@ConditionalOnMissingBean (org.springframework.boot.autoconfigure.condition)
@ConditionalOnMissingFilterBean (org.springframework.boot.autoconfigure.web.servlet)
@Profile (org.springframework.context.annotation)
@ConditionalOnInitializedRestarter (org.springframework.boot.devtools.restart)
@ConditionalOnGraphQlSchema (org.springframework.boot.autoconfigure.graphql)
@ConditionalOnJava (org.springframework.boot.autoconfigure.condition)
3. 属性绑定
@ConfigurationProperties: 声明组件的属性和配置文件哪些前缀开始项进行绑定
@EnableConfigurationProperties:快速注册注解:
场景:SpringBoot默认只扫描自己主程序所在的包。如果导入第三方包,即使组件上标注了 @Component、@ConfigurationProperties 注解,也没用。因为组件都扫描不进来,此时使用这个注解就可以快 速进行属性绑定并把组件注册进容器
将容器中任意组件(Bean)的属性值 和配置文件 的配置项的值进行绑定
1、给容器中注册组件(@Component、@Bean)
2、使用@ConfigurationProperties 声明组件和配置文件的哪些配置项进行绑定
更多注解参照:Spring注解驱动开发 【1-26集】
待复习常用注解(视频1-26集):
核心容器:
组件注册:
@Congfiguration:
基于xml的注入方式,需要根据标签逐渐赋值注入容器的。
都有注入容器的功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package com.example.annotationstudy.config; import org.springframework.core.io.Resource; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.ClassMetadata; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.core.type.filter.TypeFilter; import java.io.IOException; import java.lang.annotation.Annotation; public class MytypeFilter implements TypeFilter { @Override // MetadataReader读取到当前真正扫描的类,MetadataReaderFactory可以获取其他任何类的信息 public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { // 获取当前正在扫描的类的注解信息 AnnotationMetadata annotation= metadataReader.getAnnotationMetadata(); //获取当前真正扫描类的类信息,比如它的类型是什么,实现了什么接口 ClassMetadata classMetadata = metadataReader.getClassMetadata(); //获取当前类资源信息(类路径) Resource resource = metadataReader.getResource(); String className =classMetadata.getClassName(); System.out.println("--->"+className); if(className.contains("er")) return true; return false; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 package com.example.annotationstudy; import com.example.annotationstudy.config.test; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; //SpringApplication.run(AnnotationStudyApplication.class, args); public class AnnotationStudyApplication { public static void main(String[] args) { ApplicationContext applicationContext = new AnnotationConfigApplicationContext(test.class); System.out.println( applicationContext.getBean(Person.class)); String[] nameType = applicationContext.getBeanNamesForType(Person.class);//查看Person类的Bean数组在容器中的id数组 for (String string:nameType) { System.out.println(string); } } // Person{name='1', age=1} // person1 @Test public void test01() { ApplicationContext applicationContext = new AnnotationConfigApplicationContext(test.class); String[] nameType = applicationContext.getBeanDefinitionNames();//查看Person类的Bean数组在容器中的id数组 for (String string:nameType) { System.out.println(string); } } /*没有加过滤器前的结果: test//@Configuration注入的组件,@Configuration注解有@Component修饰 bookController//@Controller注入的组件 bookDao//@Repository注入的组件 bookService//@Service注入的组件 person// @Bean("person")注入的组件 */ /*加了过滤器: test bookDao bookService person*/ /*自定义过滤规则的输出结果 --->com.example.annotationstudy.AnnotationStudyApplication --->com.example.annotationstudy.config.MytypeFilter --->com.example.annotationstudy.controller.BookController --->com.example.annotationstudy.dao.BookDao --->com.example.annotationstudy.Person --->com.example.annotationstudy.service.bookService org.springframework.context.annotation.internalConfigurationAnnotationProcessor org.springframework.context.annotation.internalAutowiredAnnotationProcessor org.springframework.context.annotation.internalCommonAnnotationProcessor org.springframework.context.event.internalEventListenerProcessor org.springframework.context.event.internalEventListenerFactory test bookDao person 进程已结束,退出代码0*/ }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package com.example.annotationstudy.config; import com.example.annotationstudy.Person; import com.example.annotationstudy.service.bookService; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; import org.springframework.stereotype.Controller; //配置类===配置文件 @Configuration//告诉spring这是一个配置类 @ComponentScan(value="com.example.annotationstudy", includeFilters = {@ComponentScan.Filter(type = FilterType.CUSTOM,classes = MytypeFilter.class)}) //将Controller注解过滤掉 //类似xml中的包扫描标签<context:component-sacn base-package ="" > //@ComponentScan value(数组类型)指定要扫描的包, Filter[] excludeFilters();指定过滤规则; // Filter[] includeFilters()指定扫描只包含哪些注解 //FilterType.ANNOTATION按照给定的注解加载(type = FilterType.ANNOTATION,classes = Controller.class)排除/只加载Contorller注解 //FilterType.ASSIGNABLE_TYPE按照给定类型加载,type = FilterType.ASSIGNABLE_TYPE,classes = bookService.class排除/只加载bookService类型的bean //FilterType.REGEX按照正则表达式, //FilterType.CUSTOM;使用自定义规则;@ComponentScan.Filter(type = FilterType.CUSTOM,classes = MytypeFilter.class) //MytypeFilter.class返回ture或false决定是否排除/加载类到容器,如果为ture就加载/排除bean到容器内,其中value指定的路径下所有的类 // 都会在MytypeFilter中进行判断,看能否加入到容器中 public class test { // 给容器中注册一个bean,类型为方法的返回值的类型,id默认为方法名作为id,其中该注解的vaule值赋值id的优先级高于方法名 @Bean("person")//也可以这样给bean赋值为person,最后这里注入bean的id是person;类似xml中的bean标签 public Person person1() { return new Person("1",1); } }
2. 完整流程
思考:
1、SpringBoot怎么实现导一个starter、写一些简单配置,应用就能跑起来,我们无需关心整合
2、为什么Tomcat的端口号可以配置在application.properties中,并且Tomcat能启动成功?
3、导入场景后哪些自动配置能生效 ?
自动配置流程细节梳理:
**1、**导入starter-web:导入了web开发场景
1、场景启动器导入了相关场景的所有依赖:starter-json、starter-tomcat、springmvc
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 开启自动配置的核心 。
是由@Import(AutoConfigurationImportSelector.class){,@Import是一个注解,允许您将其他配置类导入到应用程序上下文中}提供功能:批量给容器中导入组件。
SpringBoot启动会默认加载 142个配置类。
这142个配置类 来自于spring-boot-autoconfigure下 META-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、以Tomcat为例:把服务器的所有配置都是以server开头的。配置都封装到了属性类中。
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又是和配置文件 进行了绑定
**效果:**导入starter、修改配置文件,就能修改底层行为。
2. YAML配置文件
痛点 :SpringBoot 集中化管理配置,application.properties
问题 :配置多以后难阅读和修改,层级结构辨识度不高
YAML 是 “YAML Ain’t a Markup Language”(YAML 不是一种标记语言)。在开发的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(是另一种标记语言)。
设计目标,就是方便人类读写
层次分明 ,更适合做配置文件
使用.yaml或 .yml作为文件后缀
1. 基本语法
大小写敏感
使用缩进表示层级关系,k: v,使用空格分割k,v
缩进时不允许使用Tab键,只允许使用空格 。换行
缩进的空格数目不重要,只要相同层级 的元素左侧对齐 即可
# 表示注释 ,从这个字符一直到行尾,都会被解析器忽略。
支持的写法:
对象 :键值对 的集合,如:映射(map)/ 哈希(hash) / 字典(dictionary)
数组 :一组按次序排列的值,如:序列(sequence) / 列表(list)
纯量 :单个的、不可再分的值,如:字符串、数字、bool、日期
2. 示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 @Component @ConfigurationProperties(prefix = "person") //和配置文件person前缀的所有配置进行绑定, //ConfigurationProperties注解可以将配置文件中的属性映射到一个Java Bean中。 @Data //自动生成JavaBean属性的getter/setter //@NoArgsConstructor //自动生成无参构造器 //@AllArgsConstructor //自动生成全参构造器 public class Person { private String name; private Integer age; private Date birthDay; private Boolean like; private Child child; //嵌套对象 private List<Dog > dogs; //数组(里面是对象) private Map<String,Cat> 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<String > text; //数组 } @Data public class Cat { private String name; private Integer age; }
properties表示法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 person.name=张三 person.age=18 person.birthDay=2010/10/12 12:12:12 person.like=true person.child.name=李四 person.child.age=12 person.child.birthDay=2018/10/12 person.child.text[0]=abc person.child.text[1]=def person.dogs[0].name=小黑 person.dogs[0].age=3 person.dogs[1].name=小白 person.dogs[1].age=2 person.cats.c1.name=小蓝 person.cats.c1.age=3 person.cats.c2.name=小灰 person.cats.c2.age=2
yaml表示法:注意数组的写法:text和dogs两种写法,cat是map的写法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 person: name: 张三 age: 18 birthDay: 2010/10/10 12:12:12 like: true child: name: 李四 age: 20 birthDay: 2018/10/10 text: ["abc","def"] #数组的2中写法,可以是text形式还可以是dogs的形式(每一个元素用一个-表示,下面的age是一个元素里 #面的内容,要同级并没有-表示。 dogs: - name: 小黑 age: 3 - name: 小白 age: 2 cats: c1: name: 小蓝 age: 3 c2: {name: 小绿,age: 2} #对象也可用{}表示
3. 细节
birthDay 推荐写为 birth-day,@ConfigurationProperties(prefix = “person”) //和配置文件person前缀的所有配置进行绑定,
文本 :
单引号 不会转义【\n 则为普通字符串显示】
双引号 会转义【\n会显示为换行符 】
大文本
|开头,大文本写在下层,保留文本格式 ,换行符正确显示
开头,大文本写在下层,折叠换行符
多文档合并
使用—可以把多个yaml文档合并在一个文档中,每个文档区依然认为内容独立
4. 小技巧:lombok
简化JavaBean 开发。自动生成构造器、getter/setter、自动生成Builder模式等
1 2 3 4 5 <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>compile</scope> </dependency>
使用@Data等注解
3. 日志配置
规范:项目开发不要编写System.out.println(),应该用日志 记录信息
感兴趣日志框架关系与起源可参考 :https://www.bilibili.com/video/BV1gW411W76m 视频 21~27集
1. 简介
Spring使用commons-logging作为内部日志,但底层日志实现是开放的。可对接其他日志框架。
spring5及以后 commons-logging被spring直接自己写了。
支持 jul,log4j2,logback。SpringBoot 提供了默认的控制台输出配置,也可以配置输出为文件。
logback是默认使用的。
虽然日志框架很多 ,但是我们不用担心,使用 SpringBoot 的默认配置就能工作的很好 。
SpringBoot怎么把日志默认配置好的
1、每个starter场景,都会导入一个核心场景spring-boot-starter
2、核心场景引入了日志的所用功能spring-boot-starter-logging(eg:spring-boot-starter-web->spring-boot-starter->spring-boot-starter-logging)
3、默认使用了logback + slf4j 组合作为默认底层日志
4、日志是系统一启动就要用,xxxAutoConfiguration是系统启动好了以后放好的组件,后来用的。
5、日志是利用监听器机制 配置好的。ApplicationListener。
6、日志所有的配置都可以通过修改配置文件实现。以logging开始的所有配置。
2. 日志格式
1 2 2023-03-31T13:56:17.511+08:00 INFO 4944 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2023-03-31T13:56:17.511+08:00 INFO 4944 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.7]
默认输出格式:
时间和日期:毫秒级精度
日志级别:ERROR,WARN,INFO,DEBUG, orTRACE.
进程 ID(可以在终端使用jps列出当前java程序的进程)
—: 消息分割符
线程名: 使用[]包含
Logger 名: 通常是产生日志的类名
消息: 日志记录的内容
注意: logback 没有FATAL级别,对应的是ERROR
默认值:参照:spring-boot包additional-spring-configuration-metadata.json文件
默认输出格式值:%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.SSS} %-5level [%thread] %logger{15} ===> %msg%n’
3. 记录日志
1 2 3 Logger logger = LoggerFactory.getLogger(getClass());//使用日志工厂生成日志类 log.info("info 日志..... 参数a:{} b:{}",a,b); 或者使用Lombok的@Slf4j注解,在该类上使用,就可以不用日志工厂生成日志类直接使用log
4. 日志级别
由低到高: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 级别
5. 日志分组
比较有用的技巧是:
将相关的logger分组在一起,统一配置。SpringBoot 也支持。比如:Tomcat 相关的日志统一设置
1 2 3 4 logging.group.tomcat=org.apache.catalina,org.apache.coyote,org.apache.tomcat //这里是将org.apache.catalina,org.apache.coyote,org.apache.tomcat2个包当成一个组为tomcat logging.level.tomcat=trace //这里是将tomcat组的日志级别设置为trace级别
SpringBoot 预定义两个组
6. 文件输出
1 2 3 4 5 6 7 #指定文件路径路径,日志文件默认叫spring.log #logging.file.path=D:\\ #指定日志文件的名,只写名字就在当前项目的同位置生成相对应的日志文件。 #也可以写名字+路径,就会在指定的路径下生成相应的日志文件然后把日志写进去 logging.file.name #file.name和file.path同时存在,只有file.name生效
SpringBoot 默认只把日志写在控制台,如果想额外记录到文件,可以在application.properties中添加logging.file.name or logging.file.path配置项。
logging.file.name
logging.file.path
示例
效果
未指定
未指定
仅控制台输出
指定
未指定
my.log
写入指定文件。可以加路径
未指定
指定
/var/log
写入指定目录,文件名为spring.log
指定
指定
以logging.file.name为准
7. 文件归档与滚动切割
归档:每天的日志单独存到一个文档中。
切割:每个文件10MB,超过大小切割成另外一个文件。
每天的日志应该独立分割出来存档。如果使用logback(SpringBoot 默认整合),可以通过application.properties/yaml文件指定日志滚动规则。
如果是其他日志系统,需要自行配置(添加log4j2.xml或log4j2-spring.xml)
不用特意配置日志,会使用默认的日志
支持的滚动规则设置如下
| 配置项 | 描述 |
| — | — |
| 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). |
8. 自定义配置
通常我们配置 application.properties 就够了。当然也可以自定义。比如:
日志系统
自定义
Logback
logback-spring.xml,logback-spring.groovy,
logback.xml, or logback.groovy
Log4j2
log4j2-spring.xmlorlog4j2.xml
JDK (Java Util Logging)
logging.properties
如果可能,我们建议您在日志配置中使用-spring 变量(例如,logback-spring.xml 而不是 logback.xml)。如果您使用标准配置文件,spring 无法完全控制日志初始化。
最佳实战:自己要写配置,配置文件名加上 xx-spring.xml
9. 切换日志组合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter</artifactId > <exclusions > <exclusion > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-logging</artifactId > </exclusion > </exclusions > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-log4j2</artifactId > </dependency >
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
10. 最佳实战
导入任何第三方框架,先排除它的日志包,因为Boot底层控制好了日志
修改 application.properties 配置文件,就可以调整日志的所有行为。如果不够,可以编写日志框架自己的配置文件放在类路径下就行,比如logback-spring.xml,log4j2-spring.xml
如需对接专业日志系统 ,也只需要把 logback 记录的日志 灌倒** kafka**之类的中间件,这和SpringBoot没关系,都是日志框架自己的配置,修改配置文件即可
业务中使用slf4j-api记录日志。不要再 sout 了
2、SpringBoot3-Web开发
SpringBoot的Web开发能力,由SpringMVC 提供。
1. Web场景
1、整合web场景
1 2 3 4 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
2、引入了 autoconfigure功能(是spring-boot-starter-web下的spring-boot-starter里面的一个依赖)
3、springboot起点的时候会加载一个@EnableAutoConfiguration注解(@SpringBootApplication注解里被@EnableAutoConfiguration注解修饰)。@EnableAutoConfiguration注解使用@Import(AutoConfigurationImportSelector.class)批量导入组件,加载 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中配置的所有组件
5、所有自动配置类如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration ====以下是响应式web场景和现在的没关系====== org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.ReactiveMultipartAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.WebSessionIdResolverAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration ================以上没关系================= org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
6、绑定了配置文件的一堆配置项
1、SpringMVC的所有配置 spring.mvc
2、Web场景通用配置 spring.web
3、文件上传配置 spring.servlet.multipart
4、服务器的配置 server: 比如:编码方式
2. 默认效果
默认配置:
包含了 ContentNegotiatingViewResolver 和 BeanNameViewResolver 组件,方便视图解析
默认的静态资源处理机制 : 静态资源放在 static 文件夹下即可直接访问
自动注册 了 Converter ,GenericConverter,Formatter 组件,适配常见数据类型转换 和格式化需求
支持 HttpMessageConverters ,可以方便返回 json等数据类型(比如: @RestController 注解会自动将处理方法的返回值序列化为 JSON式的响应体,并将其发送给客户端。)
注册 MessageCodesResolver,方便 国际化 及错误消息处理
支持 静态 index.html
自动使用 ConfigurableWebBindingInitializer,实现消息处理、数据绑定(比如将前段传递过来的参数和某些bean绑定)、类型转化、数据校验等功能
重要:
如果想保持 boot mvc 的默认配置 ,并且自定义更多的 mvc 配置,如: interceptors , formatters , view controllers 等。可以使用@Configuration注解添加一个 WebMvcConfigurer 类型的配置类,并不要标注 @EnableWebMvc
如果想保持 boot mvc 的默认配置,但要自定义核心组件实例,比如:RequestMappingHandlerMapping, RequestMappingHandlerAdapter, 或ExceptionHandlerExceptionResolver,给容器中放一个 WebMvcRegistrations 组件即可
如果想全面接管 Spring MVC,@Configuration 标注一个配置类,并加上 @EnableWebMvc注解,
实现 WebMvcConfigurer 接口
WebMvcAutoConfiguration原理
WebMvcAutoConfiguration 是 Spring Boot 中的一个自动配置类,它负责自动配置 Spring MVC(Model-View-Controller)的相关功能。
在 Spring Boot 应用程序中,当您引入了 spring-boot-starter-web 依赖时,WebMvcAutoConfiguration 将自动生效。它会根据应用程序的类路径和配置属性来自动配置 Spring MVC。它是@EnableAutoConfiguration注解使用@Import(AutoConfigurationImportSelector.class)批量导入组件中的一个组件。
1. 生效条件
1 2 3 4 5 6 7 8 9 @AutoConfiguration(after = { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class }) //在这些自动配置之后, @ConditionalOnWebApplication(type = Type.SERVLET) //如果是web应用就生效,类型是SERVLETweb,其他还有、REACTIVE 响应式web @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class }) @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) //容器中没有这个Bean,才生效。默认就是没有 @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)//优先级 @ImportRuntimeHints(WebResourcesRuntimeHints.class) public class WebMvcAutoConfiguration { }
2. WebMvcAutoConfiguration产生的效果
放了两个Filter:
HiddenHttpMethodFilter;页面表单提交Rest请求(GET、POST、PUT、DELETE)Spring MVC过滤器-HiddenHttpMethodFilter_Korbin Luo的博客-CSDN博客
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 某些场景下,例如 HTML 表单只支持 GET 和 POST 方法,无法直接发送 PUT 或 DELETE 请求。 为了解决这个问题,Spring Framework 提供了 HiddenHttpMethodFilter 过滤器。 HiddenHttpMethodFilter 允许您在 HTML 表单中使用一个隐藏字段(通常命名为 "_method") 来指定要使用的 HTTP 方法,例如 PUT 或 DELETE。当表单提交时,过滤器会检查隐藏字段的值,并将请求方法转换为相应的 HTTP 方法。 <form action="/example" method="post"> <input type="hidden" name="_method" value="put"> <!-- 其他表单字段 --> <button type="submit">提交</button> </form> 在上面的示例中,隐藏字段 _method 的值被设置为 "put",表示要使用 PUT 方法进行请求。 在您的控制器方法中,使用 @RequestMapping 注解或其他合适的注解来处理相应的请求方法。 @RequestMapping(value = "/example", method = RequestMethod.PUT) public ResponseEntity<String> updateExample() { // 处理 PUT 请求 }
FormContentFilter: 表单内容Filter,GET(数据放URL后面)、POST(数据放请求体)请求可以携带数据,PUT、DELETE 的请求体数据会被忽略.为了让put和delete的请求体内容不会被忽略,可以加一个FormContentFilter。
给容器中放了WebMvcConfigurer组件(WebMvcAutoConfigurationAdapter);给SpringMVC添加各种定制功能
1 2 3 4 5 6 7 8 @Configuration(proxyBeanMethods = false) @Import(EnableWebMvcConfiguration.class) //额外导入了其他配置 @EnableConfigurationProperties({ WebMvcProperties.class, WebProperties.class }) @Order(0) public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware{ }
所有的功能最终会和配置文件进行绑定
WebMvcProperties: spring.mvc配置文件
WebProperties: spring.web配置文件
1 2 3 4 5 6 @ConfigurationProperties( prefix = "spring.mvc" )//@ConfigurationProperties 注解的 prefix 属性被设置为 "spring.mvc",表示要绑定以 "spring.mvc" 开头的配置属性。 使用 @ConfigurationProperties 注解可以将配置文件中的属性值与 Java 类中的字段或方法关联起来, 从而方便地获取、设置和管理配置属性。
WebMvcAutoConfiguration中的WebMvcAutoConfigurationAdapter属性实现了该接口。
提供了配置SpringMVC底层的所有组件入口
2. 静态资源
1. 默认规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { if (!this.resourceProperties.isAddMappings()) { logger.debug("Default resource handling disabled"); return; } //1、 addResourceHandler(registry, this.mvcProperties.getWebjarsPathPattern(), "classpath:/META-INF/resources/webjars/"); addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> { registration.addResourceLocations(this.resourceProperties.getStaticLocations()); if (this.servletContext != null) { ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION); registration.addResourceLocations(resource); } }); } //
规则一:访问: /webjars/**路径就去 classpath:/META-INF/resources/webjars/下找资源.
1 2 3 4 5 6 7 8 /webjars/ 是一个特定的路径前缀,用于在Web应用程序中访问 WebJars 资源。 WebJars 是一种将前端库以 JAR 文件的形式打包并发布到 Maven 仓库的方式。它们允许您通过 Maven 或 Gradle 等构建工具来管理和引入前端库,就像管理其他 Java 依赖项一样。 当您在 Maven 项目中引入 WebJars 依赖时,它们将被下载并存储在您的项目的 Maven 依赖目录中。 为了在 Web 应用程序中有效地访问这些资源,Spring Boot 提供了一个特殊的处理器来映射 /webjars/ 路径。
maven 导入依赖
规则二:访问: /**路径就去 静态资源默认的四个位置找资源
classpath:/META-INF/resources/
classpath:/resources/
classpath:/static/
classpath:/public/
规则三:静态资源默认都有缓存规则的设置
所有缓存的设置,直接通过配置文件 : spring.web
cachePeriod: 缓存周期; 多久不用找服务器要新的。 默认没有,以s为单位
cacheControl: HTTP缓存 控制;https://develop er.mozilla.org/zh-CN/docs/Web/HTTP/Caching
useLastModified :是否使用最后一次修改。配合HTTP Cache规则
如果浏览器访问了一个静态资源 index.js,如果服务这个资源没有发生变化,下次访问的时候就可以直接让浏览器用自己缓存中的东西,而不用给服务器发请求
1 2 3 registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod())); registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl()); registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified());
1. 静态资源映射
静态资源映射规则在WebMvcAutoConfiguration中进行了定义:
/webjars/**的所有路径 资源都在classpath:/META-INF/resources/webjars/
/**的所有路径 资源都在classpath:/META-INF/resources/、classpath:/resources/、classpath:/static/、classpath:/public/
所有静态资源都定义了缓存规则。【浏览器访问过一次,就会缓存一段时间】,但此功能参数无默认值
period: 缓存间隔。 默认 0S;
cacheControl:缓存控制。 默认无;
useLastModified:是否使用lastModified头。 默认 false;
2. 静态资源缓存
如前面所述
所有静态资源都定义了缓存规则。【浏览器访问过一次,就会缓存一段时间】,但此功能参数无默认值
period: 缓存间隔。 默认 0S;
cacheControl:缓存控制。 默认无;
useLastModified:是否使用lastModified头。 默认 false;
3. 欢迎页
欢迎页规则在WebMvcAutoConfiguration中进行了定义:
在静态资源 目录下找 index.html
没有就在 templates下找index模板页
4. Favicon
在静态资源目录下找 favicon.ico
5. 缓存实验
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 server.port =9000 spring.web.resources.add-mappings =true spring.web.resources.cache.cachecontrol.max-age =7200 spring.web.resources.cache.use-last-modified =true
2. 自定义静态资源规则
自定义静态资源路径、自定义缓存规则
1. 配置方式
spring.mvc: 静态资源访问前缀路径
spring.web :
2. 代码方式
容器中只要有一个 WebMvcConfigurer 组件。配置的底层行为都会生效
@EnableWebMvc //禁用boot的默认配置
5. EnableWebMvcConfiguration 源码
也是WebMvcAutoConfiguration的内部类
1 2 3 4 5 6 7 8 9 @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(WebProperties.class) public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware { }
HandlerMapping: 根据请求路径 找那个handler能处理请求
SpringMVC工作原理之处理映射[HandlerMapping]
SpringMVC 内部是根据 HandlerMapping 将 Request 和 Controller 里面的方法对应起来的
WelcomePageHandlerMapping(webmvcAutoConfiguration里面注入所依赖的bean):
访问 /**路径下的所有请求,都在以前四个静态资源路径下找,欢迎页也一样
找index.html:只要静态资源的位置有一个 index.html页面,项目启动默认访问(意思是如果静态资源里面有index.html,那么项目启动也就是根路径默认跳转到index.html)
Favicon :网站图标
在静态资源目录下找 favicon.ico
理解:springboot启动成功后会在静态资源文件中寻找favicon.ico文件,如果存在这个文件就把网站的图标设置为这个,当然这里的favicon.ico文件名要正确。如果删除了favicon.ico文件,但是浏览器的图标仍然存在,那是因为浏览器的缓存机制缓存了该图标。
5. 缓存实验
1.跟缓存有关的配置是spring.web前缀的配置属性 。
2.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @ConfigurationProperties 是一个 Spring Boot 注解,用于将配置属性绑定到 Java 类的字段上。 在 Spring Boot 应用程序中,配置属性通常存储在配置文件(如 application.properties 或 application.yml)中。@ConfigurationProperties 注解可以使用在一个类上,用于将配置文件中的 属性值绑定到该类的字段上,实现属性的自动注入。 @ConfigurationProperties("spring.web")是将以 "spring.web" 为前缀的配置属性绑定到 Java 类 的字段上。 使用 @ConfigurationProperties 需要进行以下步骤: 创建一个普通的 Java 类,该类的字段将用于存储配置属性的值。 在该类上添加 @ConfigurationProperties 注解,并指定要绑定的配置属性的前缀。 @Configuration @ConfigurationProperties(prefix = "myapp") public class MyAppProperties { private String name; private String version; // 其他属性及对应的 getter 和 setter 方法 } 在上述示例中,MyAppProperties 类使用 @ConfigurationProperties 注解,并指定了 prefix 属性为 "myapp"。这意味着该类的字段将与以 "myapp" 为前缀的配置属性进行绑定。 假设在配置文件中有以下属性: myapp.name=My App myapp.version=1.0.0 通过使用 @ConfigurationProperties 注解,可以将 "My App" 绑定到 name 字段上, 将 "1.0.0" 绑定到 version 字段上。
3.spring.web可以配置国际化的区域信息,配置静态资源策略(开启,处理链,缓存),
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 server.port=9000 #1、spring.web: # 1.配置国际化的区域信息 # 2.静态资源策略(开启、处理链、缓存) #开启静态资源映射规则,默认结果为true,spring.web.resources.add-mappings和Resources类 #的addMappings属性对应。 spring.web.resources.add-mappings=true #设置缓存,设置了缓存时间为3600s #spring.web.resources.cache.period=3600 ##缓存详细合并项控制,覆盖period配置: ## 浏览器第一次请求服务器,服务器告诉浏览器此资源缓存7200秒,7200秒以内的所有此资源访问不用发给服务器请求,7200秒以后发请求给服务器 spring.web.resources.cache.cachecontrol.max-age=7200 #使用资源 last-modified 时间,来对比服务器和浏览器的资源是否相同没有变化。相同返回 304 spring.web.resources.cache.use-last-modified=true
2. 自定义静态资源规则
自定义静态资源路径、自定义缓存规则
1. 配置方式
spring.mvc: 静态资源访问前缀路径
spring.web :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 spring.web.resources.add-mappings =true spring.web.resources.cache.period =3600 spring.web.resources.cache.cachecontrol.max-age =7200 spring.web.resources.cache.cachecontrol.cache-public =true spring.web.resources.cache.use-last-modified =true spring.web.resources.static-locations =classpath:/a/,classpath:/b/,classpath:/static/ spring.mvc.webjars-path-pattern =/wj/** spring.mvc.static-path-pattern =/static/**
2. 代码方式
容器中只要有一个 WebMvcConfigurer 组件。配置的底层行为都会生效
@EnableWebMvc //禁用boot的默认配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 //@EnableWebMvc //禁用boot的默认配置,添加了就会禁用boot的默认配置的规则。 //,没有添加就会保留以前的规则和自己新写的规则 @Configuration //这是一个配置类 public class MyConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { //保留以前规则 //自己写新的规则。 registry.addResourceHandler("/static/**") .addResourceLocations("classpath :/a/","classpath:/b/") .setCacheControl(CacheControl.maxAge(1180, TimeUnit.SECONDS)); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Configuration //这是一个配置类,给容器中放一个 WebMvcConfigurer 组件,就能自定义底层 public class MyConfig /*implements WebMvcConfigurer*/ { @Bean public WebMvcConfigurer webMvcConfigurer(){ return new WebMvcConfigurer() { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/static/**") .addResourceLocations("classpath :/a/", "classpath:/b/") .setCacheControl(CacheControl.maxAge(1180, TimeUnit.SECONDS)); } }; } }
WebMvcAutoConfiguration 是一个自动配置类,它里面有一个 EnableWebMvcConfiguration
EnableWebMvcConfiguration继承与 DelegatingWebMvcConfiguration,这两个都生效
DelegatingWebMvcConfiguration利用 ID 把容器中 所有 WebMvcConfigurer 注入进来
别人调用 DelegatingWebMvcConfiguration
的方法配置底层规则,而它调用所有 WebMvcConfigurer的配置底层方法。
3. 路径匹配
Spring5.3 之后加入了更多的请求路径匹配的实现策略;
以前只支持 AntPathMatcher 策略, 现在提供了 PathPatternParser 策略。并且可以让我们指定到底使用那种策略。
1. Ant风格路径用法
Ant 风格的路径模式语法具有以下规则:
*:表示任意数量 的字符。
?:表示任意一个字符 。
:表示 任意数量的目录**。
{}:表示一个命名的模式占位符 。
[]:表示字符集合 ,例如[a-z]表示小写字母。
例如:
*.html匹配任意名称,扩展名为.html的文件。
/folder1// .java匹配在folder1目录下的任意两级目录下的.java文件。
/folder2/**/*.jsp匹配在folder2目录下任意目录深度的.jsp文件。
/{type}/{id}.html匹配任意文件名为{id}.html,在任意命名的{type}目录下的文件。
注意:Ant 风格的路径模式语法中的特殊字符需要转义,如:
要匹配文件路径中的星号,则需要转义为\*。
要匹配文件路径中的问号,则需要转义为\?。
2. 模式切换
AntPathMatcher 与 PathPatternParser
PathPatternParser在 jmh 基准测试下,有 6~8 倍吞吐量提升,降低 30%~40%空间分配率
PathPatternParser 兼容 AntPathMatcher语法,并支持更多类型的路径模式
PathPatternParser "**" 多段匹配 的支持 仅允许在模式末尾使用
1 2 3 4 5 6 7 8 9 @GetMapping("/a*/b?/{p1 :[a-f]+}") public String hello(HttpServletRequest request, @PathVariable("p1") String path) { log.info("路径变量p1: {}", path); //获取请求路径 String uri = request.getRequestURI(); return uri; }
总结:
使用默认的路径匹配规则,是由 PathPatternParser 提供的
如果路径中间需要有 **,替换成ant风格路径
1 2 3 4 spring.mvc.pathmatch.matching-strategy =ant_path_matcher
4. 内容协商
一套系统适配多端数据返回
1. 多端内容适配
1. 默认规则
SpringBoot 多端内容适配 。
基于请求头内容协商 :(默认开启)
客户端向服务端发送请求,携带HTTP标准的Accept请求头 。
Accept : application/json、text/xml、text/yaml
服务端根据客户端请求头期望的数据类型 进行动态返回
**1、默认支持把对象写为json。因为默认web场景导入了jackson处理json的包;jackson-core
2、jackson也支持把数据写为xml。导入xml相关依赖**
2. 基于请求参数内容协商:(需要开启)下面的?format就是要返回数据的格式。
1. 发送请求 GET /projects/spring-boot?format=json
2. 匹配到 @GetMapping(“/projects/spring-boot”)
3. 根据参数协商 ,优先返回 json 类型数据【需要开启参数匹配设置 】
4. 发送请求 GET /projects/spring-boot?format=xml,优先返回 xml 类型数据
2. 效果演示
请求同一个接口,可以返回json和xml不同格式数据,、默认支持把对象写为json。因为默认web场景导入了jackson处理json的包;jackson-core,jackson也支持把数据写为xml。导入xml相关依赖
引入支持写出xml内容依赖
1 2 3 4 <dependency > <groupId > com.fasterxml.jackson.dataformat</groupId > <artifactId > jackson-dataformat-xml</artifactId > </dependency >
标注注解
1 2 3 4 5 6 7 8 9 @JacksonXmlRootElement // 可以写出为xml文档 @Data public class Person { private Long id; private String userName; private String email; private Integer age; }
开启基于请求参数的内容协商
1 2 3 4 spring.mvc.contentnegotiation.favor-parameter =true spring.mvc.contentnegotiation.parameter-name =type
效果
3. 配置协商规则与支持类型
修改内容协商方式
1 2 3 4 spring.mvc.contentnegotiation.favor-parameter =true spring.mvc.contentnegotiation.parameter-name =myparam
大多数 MediaType 都是开箱即用的。也可以自定义内容类型,如:
1 spring.mvc.contentnegotiation.media-types.yaml =text/yaml
2. 自定义内容返回
1. 增加yaml返回支持
导入依赖
1 2 3 4 <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-yaml</artifactId> </dependency>
把对象写出成YAML
1 2 3 4 5 6 7 8 9 10 11 12 13 public static void main(String[] args) throws JsonProcessingException { Person person = new Person(); person.setId(1L); person.setUserName("张三"); person.setEmail("aaa@qq.com"); person.setAge(18); //禁用文档的开始标记 YAMLFactory factory = new YAMLFactory().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER); ObjectMapper mapper = new ObjectMapper(factory); String s = mapper.writeValueAsString(person); System.out.println(s); }
编写配置
1 2 spring.mvc.contentnegotiation.media-types.yaml =text/yaml
增加HttpMessageConverter组件,专门负责把对象写出为yaml格式
1 2 3 4 5 6 7 8 9 @Bean public WebMvcConfigurer webMvcConfigurer(){ return new WebMvcConfigurer() { @Override //配置一个能把对象转为yaml的messageConverter public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { converters.add(new MyYamlHttpMessageConverter());//将新增的配置格式和配置类关联起来 } }; }
3. HttpMessageConverter的示例写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class MyYamlHttpMessageConverter extends AbstractHttpMessageConverter<Object> { //或者是实现 HttpMessageConverter private ObjectMapper objectMapper = null; //把对象转成yaml public MyYamlHttpMessageConverter(){ //告诉SpringBoot这个MessageConverter支持哪种媒体类型 //媒体类型 super(new MediaType("text", "yaml", Charset.forName("UTF-8"))); YAMLFactory factory = new YAMLFactory() .disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER); this.objectMapper = new ObjectMapper(factory); } @Override protected boolean supports(Class<?> clazz) { //只要是对象类型,不是基本类型 return true; } @Override //@RequestBody,读取请求对象 protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { return null; } @Override //@ResponseBody 把对象怎么写出去 protected void writeInternal(Object methodReturnValue, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { //try-with写法,自动关流 try(OutputStream os = outputMessage.getBody()){ this.objectMapper.writeValue(os,methodReturnValue); } } }
2. 思考:如何增加其他
配置媒体类型支持:
spring.mvc.contentnegotiation.media-types.yaml=text/yaml
编写对应的HttpMessageConverter,要告诉Boot这个支持的媒体类型
把MessageConverter组件加入到底层
容器中放一个WebMvcConfigurer
组件,并配置底层的MessageConverter
3. 内容协商原理-HttpMessageConverter
HttpMessageConverter 怎么工作?合适工作?
定制 HttpMessageConverter 来实现多端内容协商
编写WebMvcConfigurer提供的configureMessageConverters底层,修改底层的MessageConverter
1. @ResponseBody由HttpMessageConverter处理
标注了@ResponseBody的返回值 将会由支持它的 HttpMessageConverter写给浏览器
如果controller方法的返回值标注了 @ResponseBody 注解
请求进来先来到DispatcherServlet的doDispatch()进行处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 doDispatch() 是 Spring MVC 中的核心方法之一,用于处理请求的分发和调度。 在 Spring MVC 中,请求的处理流程如下: 1.客户端发送一个 HTTP 请求到服务器。 2.前端控制器(Front Controller)接收到请求,这个控制器通常是 DispatcherServlet,它是 Spring MVC 的核心组件。 3.DispatcherServlet 根据请求的 URL 找到相应的处理器(Handler)。 4.处理器执行相应的业务逻辑并返回一个 ModelAndView 对象。(其中执行目标方法要准备参数解析器和 返回值解析器) 5.DispatcherServlet 根据 ModelAndView 对象选择合适的视图(View)进行渲染。 最终将渲染结果返回给客户端。( 在这个处理流程中,doDispatch() 方法负责实际执行第 4 步和第 5 步的操作。 具体而言,doDispatch() 方法会执行以下主要任务: 解析请求,包括解析请求的方法、路径、参数等信息。 根据请求的方法和路径,确定匹配的处理器(Handler)。 执行处理器的业务逻辑,处理请求并返回一个 ModelAndView 对象。 根据 ModelAndView 对象中的视图名字,选择合适的视图(View)进行渲染。 渲染视图,生成最终的响应结果。 doDispatch() 方法的具体实现会涉及到请求的映射、处理器适配器、处理器拦截器、参数解析器等等。 该方法是 Spring MVC 框架的核心之一,在请求的处理过程中起到了关键的作用。 需要注意的是,doDispatch() 方法通常由 DispatcherServlet 自动调用,开发者一般不需要直接调用 该方法。开发者主要关注配置合适的请求映射、编写处理器方法以及配置视图解析器等,框架会自动根据 配置和规则来调用 doDispatch() 方法来处理请求。
找到一个 HandlerAdapter 适配器。利用适配器执行目标方法
RequestMappingHandlerAdapter来执行,调用invokeHandlerMethod()来执行目标方法
目标方法执行之前,准备好两个东西
HandlerMethodArgumentResolver:参数解析器,确定目标方法每个参数值
HandlerMethodReturnValueHandler:返回值处理器,确定目标方法的返回值改怎么处理
RequestMappingHandlerAdapter 里面的invokeAndHandle()真正执行目标方法
目标方法执行完成,会返回返回值对象
**找到一个合适的返回值处理器 **HandlerMethodReturnValueHandler
最终找到 RequestResponseBodyMethodProcessor能处理 标注了 @ResponseBody注解的方法
RequestResponseBodyMethodProcessor 调用writeWithMessageConverters ,利用MessageConverter把返回值写出去
上面解释:@ResponseBody由HttpMessageConverter处理
1 2 3 HttpMessageConverter 是 Spring MVC 中的一个关键接口,用于处理 HTTP 请求和响应消息的转换。 它负责将 Java 对象与 HTTP 请求/响应的内容进行转换,使得应用程序可以方便地处理不同类型的数据格式, 如 JSON、XML、表单数据等。
HttpMessageConverter 会先进行内容协商
遍历所有的MessageConverter看谁支持这种内容类型的数据
默认MessageConverter有以下
最终因为要json所以MappingJackson2HttpMessageConverter支持写出json
jackson用ObjectMapper(是一个类)把对象写出去
2. WebMvcAutoConfiguration提供几种默认HttpMessageConverters
EnableWebMvcConfiguration通过 addDefaultHttpMessageConverters添加了默认的MessageConverter;如下:
ByteArrayHttpMessageConverter: 支持字节数据读写
StringHttpMessageConverter: 支持字符串读写
ResourceHttpMessageConverter:支持资源读写
ResourceRegionHttpMessageConverter: 支持分区资源写出
AllEncompassingFormHttpMessageConverter:支持表单xml/json读写
MappingJackson2HttpMessageConverter: 支持请求响应体Json读写
默认8个:
系统提供默认的MessageConverter 功能有限,仅用于json或者普通返回数据。额外增加新的内容协商功能,必须增加新的HttpMessageConverter
5. 模板引擎
由于 SpringBoot 使用了嵌入式 Servlet 容器 。所以 JSP 默认是不能使用 的。
如果需要服务端页面渲染 ,优先考虑使用 模板引擎。
模板引擎页面默认放在 src/main/resources/templates
SpringBoot 包含以下模板引擎的自动配置
FreeMarker
Groovy
Thymeleaf
Mustache
Thymeleaf官网 :https://www.thymeleaf.org/
1 2 3 4 5 6 7 8 9 10 11 <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>Good Thymes Virtual Grocery</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <link rel="stylesheet" type="text/css" media="all" th:href="@{/css/gtvg.css}" /> </head> <body> <p th:text="#{home.welcome}">Welcome to our grocery store!</p> </body </html>
1. Thymeleaf整合(是前后端一体的开发)
1 2 3 4 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
自动配置原理
开启了org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration自动配置
属性绑定在 ThymeleafProperties类 中,对应配置文件 spring.thymeleaf 内容(也就是配置文件中以spring.thymeleaf开头的属性)
所有的模板页面默认在 classpath:/templates文件夹下
默认效果
所有的模板页面在 classpath:/templates/下面找
找后缀名为.html的页面
事例:
1.使用Thymeleaf来处理的项目其controller类用@Controller //适配 服务端渲染 前后不分离模式开始;而前后端分离的项目用@Resttroller注解
Spring 注解之@RestController与@Controller的区别 - 楼兰胡杨 - 博客园
1 2 3 4 5 @RestController无法返回指定页面,而@Controller可以;前者可以直接返回数据, 后者需要@ResponseBody辅助。 如果需要返回JSON,XML或自定义mediaType内容到页面,@RestController自己就可以搞定, 这个注解对于返回数据比较方便,因为它会自动将对象实体转换为JSON格式。而@Controller需要在对应的 方法加上@ResponseBody注解。
2.get请求其参数是写在url中的,而post请求参数是在请求体中的。
3.@GetMapping()用来修饰处理get请求的方法。
4.//模板的逻辑视图名
//物理视图 = 前缀 + 逻辑视图名 + 后缀
//真实地址 = classpath:/templates/welcome.html其中的前缀和后缀在ThymeleafProperties类中指定了的
5.其Thymeleaf还提供了一些工具类,常用#类名表示,例如:#string。这些工具类可以进行简单的运行操作。
6.如果html中要使用Thymeleaf语法,需要提供其命名空间
2. 基础语法(在html中使用Thymeleaf)
1. 核心用法
导入命名空间:
1 2 3 <html lang="en" xmlns:th="http://www.thymeleaf.org"> xmlns:th属性,该属性是Thymeleaf命名空间(http://www.thymeleaf.org)的声明。 这个命名空间允许您在HTML代码中使用Thymeleaf特定的属性和表达式。
th:xxx:动态渲染指定的 html 标签属性值、或者th指令(遍历、判断等)
th:text:标签体内文本值渲染
th:utext:不会转义,显示为html原本的样子。也就是文本中的html元素可以被解析
th:属性:标签指定属性渲染
th:attr:标签任意属性渲染
th:if (根据后端逻辑或数据的条件来控制是否在渲染过程中包含或显示某个元素。) th:each…:其他th指令
例如:
1 2 3 4 <p th:text="${content}">原内容</p> <a th:href="${url}">登录</a> <img src="../../images/gtvglogo.png" th :attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />
表达式:用来动态取值
${}:变量取值;使用model共享给页面的值都直接用${}是用下面的方法实现的:
model.addAttribute(“imgUrl”, “/4.jpg”);imgUrl就是共享的值
@{}:url路径;@{}是一个Thymeleaf的URL表达式,它用来在HTML文档中生成一个相对于当前应用上下文的URL路径。当然也可以直接使用{}表示,例如:src="{}"但是其如果在配置文件中配置了 server.servlet.context-path=/demo那么要想配置的src路径生效需要在src的路径前添加/demo前缀否则无法访问,如果是使用的是src=的是@{}那么可以不用修改src仍旧可以正常访问。
**@{}路径中的内容会自动添加链接头部和尾部 **
#{}:国际化消息
~{}:片段引用
*{}:变量选择:需要配合th:object绑定对象
系统工具&内置对象: 详细文档
param:请求参数对象
session:session对象
application:application对象
#execInfo:模板执行信息
#messages:国际化消息
#uris:uri/url工具
#conversions:类型转换工具
#dates:日期工具,是java.util.Date对象的工具类
#calendars:类似#dates,只不过是java.util.Calendar对象的工具类
#temporals: JDK8+ java.time API 工具类
#numbers:数字操作工具
#strings:字符串操作
#objects:对象操作
#bools:bool操作
#arrays:array工具
#lists:list工具
#sets:set工具
#maps:map工具
#aggregates:集合聚合工具(sum、avg)
#ids:id生成工具
2. 语法示例
表达式:
变量取值:${…}
url 取值:@{…}
国际化消息:#{…}
变量选择:*{…}
片段引用:~{…}
常见:
文本:‘one text’,‘another one!’,…
数字:0,34,3.0,12.3,…
布尔:true、false
null:null
变量名:one,sometext,main…
文本操作:
拼串:+
文本替换:| The name is ${name} |
布尔操作:
比较运算:
比较:>,<,<=,>=(gt,lt,ge,le)
等值运算:==,!=(eq,ne)
条件运算:
if-then:(if)?(then)
if-then-else:(if)?(then):(else)
default:(value)?:(defaultValue)
特殊语法:
所有以上都可以嵌套组合
1 'User is of type ' + (${user.isAdmin()} ? 'Administrator' : (${user.type} ?: 'Unknown'))
3. 属性设置
th:href=“@{/product/list}”
th:attr=“class=${active}”
th:attr=“src=@{/images/gtvglogo.png},title=${logo},alt=#{logo}”
th:checked=“${user.active}”
1 2 3 4 <p th:text="${content}">原内容</p> <a th:href="${url}">登录</a> <img src="../../images/gtvglogo.png" th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />
4. 遍历
语法: th:each=“元素名,迭代状态 : ${集合}”
1 2 3 4 5 6 7 8 9 10 11 <tr th:each="prod : ${prods}"> <td th:text="${prod.name}">Onions</td> <td th:text="${prod.price}">2.41</td> <td th:text="${prod.inStock}? #{true} : #{false}">yes</td> </tr> <tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'"> <td th:text="${prod.name}">Onions</td> <td th:text="${prod.price}">2.41</td> <td th:text="${prod.inStock}? #{true} : #{false}">yes</td> </tr>
iterStat 有以下属性:
index:当前遍历元素的索引,从0开始
count:当前遍历元素的索引,从1开始
size:需要遍历元素的总数量
current:当前正在遍历的元素对象
even/odd:是否偶数/奇数行
first:是否第一个元素
last:是否最后一个元素
1 2 <th scope="col">#</th>这里的th标签表示是一个表头标签,scope属性指定表头单元格的范围,这里的范围 是一个表头占据一个单元格。
5. 判断
th:if
1 <td th:text="| ${person.age} / ${person.age >= 18?'成年':'未成年' }|"> </td>
1 2 3 4 5 <a href="comments.html" th:href="@{/product/comments(prodId=${prod.id})}" th:if="${not #lists.isEmpty(prod.comments)}" >view</a
th:switch
1 2 3 4 5 6 7 <div th:switch="${user.role}"> <p th:case="'admin'">User is an administrator</p>//这里的admin的单引号表示其为一个字符串 //,和变量区分 <p th:case="#{roles.manager}">User is a manager</p> <p th:case="*">User is some other thing</p> </div>
6. 属性优先级
1 2 3 <ul> <li th:each="item : ${items}" th:text="${item.description}">Item description here...</li> </ul>
Order
Feature
Attributes
1
片段包含
th:insertth:replace
2
遍历
th:each
3
判断
th:ifth:unlessth:switchth:case
4
定义本地变量
th:objectth:with
5
通用方式属性修改
th:attrth:attrprependth:attrappend
6
指定属性修改
th:valueth:hrefth:src…
7
文本值
th:textth:utext
8
片段指定
th:fragment
9
片段移除
th:remove
7. 行内写法
[[…]] or [(…)]
1 <p>Hello, [[${session.user.name}]]!</p>
8. 变量选择
1 2 3 4 5 <div th:object="${session.user}"> <p>Name: <span th:text="*{firstName}">Sebastian</span>.</p> <p>Surname: <span th:text="*{lastName}">Pepper</span>.</p> <p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p> </div>
等同于
1 2 3 4 5 <div> <p>Name: <span th:text="${session.user.firstName}">Sebastian</span>.</p> <p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p> <p>Nationality: <span th:text="${session.user.nationality}">Saturn</span>.</p> </div
9. 模板布局
定义模板: th:fragment
引用模板:~{templatename::selector}
插入模板:th:insert、th:replace
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <footer th:fragment="copy">© 2011 The Good Thymes Virtual Grocery</footer> <body> <div th:insert="~{footer :: copy}"></div> <div th:replace="~{footer :: copy}"></div> </body> <body> 结果: <body> <div> <footer>© 2011 The Good Thymes Virtual Grocery</footer> </div> <footer>© 2011 The Good Thymes Virtual Grocery</footer> </body> </body>
1 2 3 th:insert和th:replace是Thymeleaf模板引擎中的两个属性,用于在模板中插入或替换其他模板片段。 th:insert属性用于在当前模板中插入另一个模板片段。 th:replace属性用于在当前模板中替换自身的内容为另一个模板片段。
1 2 3 4 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency>
修改页面后;ctrl+F9刷新效果(热启动);前提是配置了devtools。
java代码的修改,如果devtools热启动了,可能会引起一些bug,难以排查
(缓存机制)
spring.thymeleaf.check-template=false检查模板是否存在
6. 国际化
国际化的自动配置参照MessageSourceAutoConfiguration
实现步骤 :
Spring Boot 在类路径根下查找messages资源绑定文件(配置。文件名为:messages.properties
多语言可以定义多个消息文件,命名为messages_区域代码.properties。如:
messages.properties:默认
messages_zh_CN.properties:中文环境
messages_en_US.properties:英语环境
在程序中 可以自动注入 MessageSource组件,获取国际化的配置项值
在页面中 可以使用表达式 #{}获取国际化的配置项值
1 2 3 4 5 6 7 8 9 10 11 @Autowired //国际化取消息用的组件 MessageSource messageSource; @GetMapping("/haha") public String haha(HttpServletRequest request){ Locale locale = request.getLocale(); //利用代码的方式获取国际化配置文件中指定的配置项的值 String login = messageSource.getMessage("login", null, locale); return login; }
7. 错误处理
1. 默认机制
错误处理的自动配置 都在ErrorMvcAutoConfiguration中,两大核心机制:
SpringBoot 会自适应处理错误 ,响应页面 或JSON数据
1 2 3 4 5 Spring Boot 会根据请求的类型和接受的媒体类型,自适应地处理错误,返回合适的响应格式。 如果请求是一个浏览器请求,Spring Boot 会返回一个 HTML 错误页面,这个页面也被称为白标错误页面。 如果请求是一个 RESTful 请求,或者接受的媒体类型是 JSON,Spring Boot 会返回一个 JSON 响应, 包含错误的详细信息,HTTP 状态码,和异常消息
2.** SpringMVC的错误处理机制依然保留,MVC处理不了 ,才会 交给boot进行处理**
1.springMvc处理错误:
这里如果发生了错误,会执行@ExceptionHandler修饰的方法,这里是将handleException中的方法的返回值写到返回体中,然后返回该浏览器。
2.
发生错误以后,转发给/error路径,SpringBoot在底层写好一个 BasicErrorController的组件,专门处理这个请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @RequestMapping(produces = MediaType.TEXT_HTML_VALUE) //返回HTML public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { HttpStatus status = getStatus(request); Map<String, Object> model = Collections .unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML))); response.setStatus(status.value()); ModelAndView modelAndView = resolveErrorView(request, response, status, model); return (modelAndView != null) ? modelAndView : new ModelAndView("error", model); } @RequestMapping //返回 ResponseEntity, JSON public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { HttpStatus status = getStatus(request); if (status == HttpStatus.NO_CONTENT) { return new ResponseEntity<>(status); } Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL)); return new ResponseEntity<>(body, status); }
1 2 3 4 //1、解析错误的自定义视图地址 ModelAndView modelAndView = resolveErrorView(request, response, status, model); //2、如果解析不到错误页面的地址,默认的错误页就是 error return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
容器中专门有一个错误视图解析器
1 2 3 4 5 6 @Bean @ConditionalOnBean(DispatcherServlet.class) @ConditionalOnMissingBean(ErrorViewResolver.class) DefaultErrorViewResolver conventionErrorViewResolver() { return new DefaultErrorViewResolver(this.applicationContext, this.resources); }
SpringBoot解析自定义错误页的默认规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @Override public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) { ModelAndView modelAndView = resolve(String.valueOf(status.value()), model); if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) { modelAndView = resolve(SERIES_VIEWS.get(status.series()), model); } return modelAndView; } private ModelAndView resolve(String viewName, Map<String, Object> model) { String errorViewName = "error/" + viewName; TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext); if (provider != null) { return new ModelAndView(errorViewName, model); } return resolveResource(errorViewName, model); } private ModelAndView resolveResource(String viewName, Map<String, Object> model) { for (String location : this.resources.getStaticLocations()) { try { Resource resource = this.applicationContext.getResource(location); resource = resource.createRelative(viewName + ".html"); if (resource.exists()) { return new ModelAndView(new HtmlResourceView(resource), model); } } catch (Exception ex) { } } return null; }
容器中有一个默认的名为 error 的 view; 提供了默认白页功能
1 2 3 4 5 @Bean(name = "error") @ConditionalOnMissingBean(name = "error") public View defaultErrorView() { return this.defaultErrorView; }
封装了JSON格式的错误信息
1 2 3 4 5 @Bean @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT) public DefaultErrorAttributes errorAttributes() { return new DefaultErrorAttributes(); }
规则:
解析一个错误页
如果发生了500、404、503、403 这些错误
如果有模板引擎 ,默认在 classpath:/templates/error/精确码.html
如果没有模板引擎,在静态资源文件夹下找 精确码.html
如果匹配不到精确码.html这些精确的错误页,就去找5xx.html,4xx.html模糊匹配
如果有模板引擎,默认在 classpath:/templates/error/5xx.html
如果没有模板引擎,在静态资源文件夹下找 5xx.html
如果模板引擎路径templates下有 error.html页面,就直接渲染
2. 自定义错误响应
1. 自定义json响应
使用@ControllerAdvice + @ExceptionHandler 进行统一异常处理
2. 自定义页面响应
根据boot的错误页面规则,自定义页面模板
3. 最佳实战
前后分离
后台发生的所有错误,@ControllerAdvice + @ExceptionHandler进行统一异常处理。
服务端页面渲染
不可预知的一些,HTTP码表示的服务器或客户端错误
给classpath:/templates/error/下面,放常用精确的错误码页面。500.html,404.html
给classpath:/templates/error/下面,放通用模糊匹配的错误码页面。 5xx.html,4xx.html
发生业务错误
核心业务 ,每一种错误,都应该代码控制,跳转到自己定制的错误页 。
通用业务 ,classpath:/templates/error.html页面,显示错误信息(可以通过在错误页面中添加[[${trance}]]来显示错误堆栈信息) 。
页面,JSON,可用的Model数据如下
8. 嵌入式容器
Servlet容器 :管理、运行Servlet组件 (Servlet、Filter、Listener)的环境,一般指服务器
1. 自动配置原理
SpringBoot 默认嵌入Tomcat作为Servlet容器。
自动配置类 是ServletWebServerFactoryAutoConfiguration,EmbeddedWebServerFactoryCustomizerAutoConfiguration
自动配置类开始分析功能。xxxxAutoConfiguration
1 2 3 4 5 6 7 8 9 10 11 12 @AutoConfiguration @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) @ConditionalOnClass(ServletRequest.class)//@ConditionOn表示满足这些条件才将该类加载到容器中 @ConditionalOnWebApplication(type = Type.SERVLET) @EnableConfigurationProperties(ServerProperties.class) @Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, ServletWebServerFactoryConfiguration.EmbeddedTomcat.class, ServletWebServerFactoryConfiguration.EmbeddedJetty.class, ServletWebServerFactoryConfiguration.EmbeddedUndertow.class }) public class ServletWebServerFactoryAutoConfiguration { }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @AutoConfiguration:该注解表示这是一个自动配置类,用于自动配置应用程序的某些功能或组件。 @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE):该注解指定了自动配置类的优先级, 使用 Ordered.HIGHEST_PRECEDENCE 表示具有最高优先级。优先级较高的自动配置类将在启动过程中优先 加载和应用。 @ConditionalOnClass(ServletRequest.class):该注解表示只有当类路径上存在 ServletRequest 类时 ,才会加载和应用该自动配置类。也就是说,只有当 Servlet 相关的类存在时,该自动配置类才会生效。 @ConditionalOnWebApplication(type = Type.SERVLET):该注解表示只有当应用程序类型为 Servlet Web 应用时,才会加载和应用该自动配置类。这意味着该自动配置类仅适用于基于 Servlet 的 Web 应用程序,而不适用于其他类型的应用程序。web程序除了servlet外还有其他的,比如:响应式,JSP等。 关于 Servlet 的特点,以下是一些主要的特点: 平台无关性:Servlet 是基于 Java 技术的,可以在任何支持 Java 的平台上运行,无论是 Windows、 Linux 还是其他操作系统。 可移植性:Servlet 可以在不同的 Web 容器中运行,例如 Tomcat、Jetty、WebLogic 等。 这使得开发人员可以在不同的容器中部署和运行 Servlet,而不需要修改代码。 @EnableConfigurationProperties(ServerProperties.class):用于启用配置属性的绑定并将其注入到 Spring 容器中。 在这种情况下,ServerProperties 类的配置属性将被绑定到该自动配置类中。 其中ServerProperties 类被@ConfigurationProperties( prefix = "server", ignoreUnknownFields = true )修饰,@ConfigurationProperties 是一个注解,用于将配置属性绑定到一个类上。在上述代码中, 它被应用在类级别上,表示将配置属性绑定到带有 prefix 前缀的属性上,并且忽略未知的字段。 但是其ServerProperties 类在此时并未注入到容器中。 @Import({ ... }):该注解用于导入其他的配置类或组件。在这个例子中,它导入了与 Servlet Web 服务器工厂相关的配置类,包括 Tomcat、Jetty 和 Undertow 的配置。
ServletWebServerFactoryAutoConfiguration 自动配置了嵌入式容器场景
绑定了ServerProperties配置类,所有和服务器有关的配置 server(也就是配置文件·中以server开头的配置属性是关于服务器的)
ServletWebServerFactoryAutoConfiguration 导入了 嵌入式的三大服务器 Tomcat、Jetty、Undertow
1.servlet不仅支持Tomcat服务器还支持Jetty ,Netty,Undertow服务器
2.
1 2 3 4 5 6 7 8 9 10 @ConditionalOnClass({Servlet.class, Tomcat.class, UpgradeProtocol.class}) @ConditionalOnMissingBean( value = {ServletWebServerFactory.class}, search = SearchStrategy.CURRENT ) static class EmbeddedTomcat { EmbeddedTomcat() { } @ConditionalOnClass({Servlet.class, Tomcat.class, UpgradeProtocol.class})决定了web程序的服务器 类型,而避免了其他类型的服务器。
导入 Tomcat、Jetty、Undertow 都有条件注解。系统中有这个类才行(也就是导了包)也是这种方式决定了web程序采用的服务器类型。
默认 Tomcat配置生效。给容器中放 TomcatServletWebServerFactory
都给容器中 ServletWebServerFactory放了一个** web服务器工厂(造web服务器的)可以在ServletWebServerFactoryAutoConfiguration类中查看到注入了不同的 ServletWebServerFactory**
1 2 @Bean TomcatServletWebServerFactory tomcatServletWebServerFactory
**web服务器工厂 都有一个功能,**getWebServer获取web服务器。其中TomcatServletWebServerFactory 创建了tomcat而其他服务器工厂没有创建。
TomcatServletWebServerFactory 创建了 tomcat。
ServletWebServerFactory 什么时候会创建 webServer出来。
ServletWebServerApplicationContextioc容器,启动的时候会调用创建web服务器
Spring**容器刷新(启动)**的时候,会预留一个时机,刷新子容器。onRefresh()
refresh() 容器刷新 十二大步的刷新子容器会调用 onRefresh();
1 2 3 4 5 6 7 8 9 10 @Override protected void onRefresh() { super.onRefresh(); try { createWebServer(); } catch (Throwable ex) { throw new ApplicationContextException("Unable to start web server", ex); } }
Web场景的Spring容器启动,在onRefresh的时候,会调用创建web服务器的方法。
Web服务器的创建是通过WebServerFactory搞定的。容器中又会根据导了什么包条件注解,启动相关的 服务器配置,默认EmbeddedTomcat会给容器中放一个 TomcatServletWebServerFactory,导致项目启动,自动创建出Tomcat。
用法:
1.修改server下的相关配置就可以修改服务器的参数
2.通过给容器中放一个ServletWebServerFactory,来禁用掉springBoot默认配置的服务器工厂,实现自定义的嵌入式服务器。
2. 自定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <properties> <servlet-api.version>3.1.0</servlet-api.version> </properties> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <!-- Exclude the Tomcat dependency --> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <!-- Use Jetty instead --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jetty</artifactId> </dependency>
3. 最佳实践
用法:
修改server下的相关配置就可以修改服务器参数
通过给容器中放一个ServletWebServerFactory ,来禁用掉SpringBoot默认放的服务器工厂,实现自定义嵌入任意服务器 。
9. 全面接管SpringMVC
SpringBoot 默认配置好了 SpringMVC 的所有常用特性。
如果我们需要全面接管SpringMVC的所有配置并禁用默认配置 ,仅需要编写一个WebMvcConfigurer配置类,并标注 @EnableWebMvc 即可
全手动模式
@EnableWebMvc : 禁用默认配置,该注解在@Configuration上
WebMvcConfigurer 组件:定义MVC的底层行为
1. WebMvcAutoConfiguration 到底自动配置了哪些规则
SpringMVC自动配置场景给我们配置了如下所有默认行为
WebMvcAutoConfigurationweb场景的自动配置类
支持RESTful的filter:HiddenHttpMethodFilter(HiddenHttpMethodFilter 的作用就是检查通过 POST 请求提交的表单数据中是否包含名为 _method 的隐藏字段,并将其值作为实际的 HTTP 方法进行处理。也就是新增了delete,和patch)
支持非POST请求,请求体携带数据:FormContentFilter(FormContentFilter 的作用是解析请求体中的表单数据,并将其转换为相应的请求参数,以便后续的处理器(Handler)能够方便地获取和使用这些参数。)
导入EnableWebMvcConfiguration :
RequestMappingHandlerAdapter
WelcomePageHandlerMapping: 欢迎页功能 支持(模板引擎目录、静态资源目录放index.html),项目访问/ 就默认展示这个页面.
RequestMappingHandlerMapping:找每个请求由谁处理的映射关系
ExceptionHandlerExceptionResolver:默认的异常解析器
LocaleResolver:国际化解析器
ThemeResolver:主题解析器
FlashMapManager:临时数据共享
FormattingConversionService: 数据格式化 、类型转化
Validator: 数据校验JSR303提供的数据校验功能
WebBindingInitializer:请求参数的封装与绑定
ContentNegotiationManager:内容协商管理器
WebMvcAutoConfigurationAdapter 配置生效,它是一个WebMvcConfigurer,定义mvc底层组件
定义好 WebMvcConfigurer底层组件默认功能;所有功能详见列表
视图解析器:InternalResourceViewResolver
视图解析器:BeanNameViewResolver,**视图名(controller方法的返回值字符串)**就是组件名
内容协商解析器:ContentNegotiatingViewResolver
请求上下文过滤器:RequestContextFilter: 任意位置直接获取当前请求
静态资源链规则
ProblemDetailsExceptionHandler:错误详情
SpringMVC内部场景异常被它捕获:
定义了MVC默认的底层行为: WebMvcConfigurer
2. @EnableWebMvc 禁用默认行为
@EnableWebMvc给容器中导入 DelegatingWebMvcConfiguration组件,
他是 WebMvcConfigurationSupport
WebMvcAutoConfiguration有一个核心的条件注解, @ConditionalOnMissingBean(WebMvcConfigurationSupport.class),容器中没有WebMvcConfigurationSupport,WebMvcAutoConfiguration才生效.
@EnableWebMvc 导入 WebMvcConfigurationSupport 导致 WebMvcAutoConfiguration 失效。导致禁用了默认行为
@EnableWebMVC 禁用了 Mvc的自动配置
WebMvcConfigurer 定义SpringMVC底层组件的功能类
定义扩展SpringMVC底层功能
提供方法
核心参数
功能
默认
addFormatters
FormatterRegistry
格式化器 :支持属性上@NumberFormat和@DatetimeFormat的数据类型转换
GenericConversionService
getValidator
无
数据校验 :校验 Controller 上使用@Valid标注的参数合法性。需要导入starter-validator
无
addInterceptors
InterceptorRegistry
拦截器 :拦截收到的所有请求
无
configureContentNegotiation
ContentNegotiationConfigurer
内容协商 :支持多种数据格式返回。需要配合支持这种类型的HttpMessageConverter
支持 json
configureMessageConverters
List<HttpMessageConverter<?>>
消息转换器 :标注@ResponseBody的返回值会利用MessageConverter直接写出去
8 个,支持byte,string,multipart,resource,json
addViewControllers
ViewControllerRegistry
视图映射 :直接将请求路径与物理视图映射。用于无 java 业务逻辑的直接视图页渲染
无
mvc:view-controller
configureViewResolvers
ViewResolverRegistry
视图解析器 :逻辑视图转为物理视图
ViewResolverComposite
addResourceHandlers
ResourceHandlerRegistry
静态资源处理 :静态资源路径映射、缓存控制
ResourceHandlerRegistry
configureDefaultServletHandling
DefaultServletHandlerConfigurer
默认 Servlet :可以覆盖 Tomcat 的DefaultServlet。让DispatcherServlet拦截/
无
configurePathMatch
PathMatchConfigurer
路径匹配 :自定义 URL 路径匹配。可以自动为所有路径加上指定前缀,比如 /api
无
configureAsyncSupport
AsyncSupportConfigurer
异步支持 :
TaskExecutionAutoConfiguration
addCorsMappings
CorsRegistry
跨域 :
无
addArgumentResolvers
List
参数解析器 :
mvc 默认提供
addReturnValueHandlers
List
返回值解析器 :
mvc 默认提供
configureHandlerExceptionResolvers
List
异常处理器 :
默认 3 个
ExceptionHandlerExceptionResolver
ResponseStatusExceptionResolver
DefaultHandlerExceptionResolver
getMessageCodesResolver
无
消息码解析器 :国际化使用
无
10. 最佳实践
SpringBoot 已经默认配置好了Web开发 场景常用功能。我们直接使用即可。
三种方式
方式
用法
效果
全自动
直接编写控制器逻辑
全部使用自动配置默认效果
手自一体
@Configuration +
配置WebMvcConfigurer +
配置 WebMvcRegistrations
不要标注
@EnableWebMvc
保留自动配置效果
手动设置部分功能
定义MVC底层组件
全手动
@Configuration +
配置WebMvcConfigurer
标注
@EnableWebMvc
禁用自动配置效果
全手动设置
总结:
给容器中写一个配置类@Configuration实现 WebMvcConfigurer但是不要标注 @EnableWebMvc注解,实现手自一体的效果。
11. Web新特性
1. Problemdetails
RFC 7807: https://www.rfc-editor.org/rfc/rfc7807
1 2 ProblemDetails 是一种在Web API中表示错误和异常信息的规范化格式。它是根据RFC 7807规范定义的, 旨在提供一种统一的方式来描述和传递错误信息。
错误信息 返回新格式
原理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Configuration(proxyBeanMethods = false) //配置过一个属性 spring.mvc.problemdetails.enabled=true才能让 //ProblemDetailsErrorHandlingConfiguration生效,其中spring.mvc.problemdetails.enabled //默认是false的,也就是ProbleDetailsErroHandlingConfiguration默认是关闭的 @ConditionalOnProperty(prefix = "spring.mvc.problemdetails", name = "enabled", havingValue = "true") static class ProblemDetailsErrorHandlingConfiguration { @Bean @ConditionalOnMissingBean(ResponseEntityExceptionHandler.class) ProblemDetailsExceptionHandler problemDetailsExceptionHandler() { return new ProblemDetailsExceptionHandler(); } }
ProblemDetailsExceptionHandler (被 @ControllerAdvice修饰的类)是一个 @ControllerAdvice集中处理系统异常,
ProblemDetailsExceptionHandler 继承 ResponseEntityExceptionHandler ,而ResponseEntityExceptionHandler类有一个@ExceptionHandle注解用于修饰ResponseEntity类,其中@ExceptionHandle**处理以下异常。如果系统出现以下异常,会被SpringBoot支持以 **RFC 7807规范方式返回错误数据(但是该规范不是默认生效的):
使用@ExceptionHandler 注解时,您需要在方法中定义异常类型作为参数,并在方法体中编写相应的逻辑来处理该异常。当发生定义的异常时,Spring Boot将调用带有 @ExceptionHandler 注解的方法,并将异常传递给该方法进行处理。您可以在该方法中执行适当的操作,例如记录日志、返回自定义错误响应等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @ExceptionHandler({ HttpRequestMethodNotSupportedException.class, //请求方式不支持 HttpMediaTypeNotSupportedException.class, HttpMediaTypeNotAcceptableException.class, MissingPathVariableException.class, MissingServletRequestParameterException.class, MissingServletRequestPartException.class, ServletRequestBindingException.class, MethodArgumentNotValidException.class, NoHandlerFoundException.class, AsyncRequestTimeoutException.class, ErrorResponseException.class, ConversionNotSupportedException.class, TypeMismatchException.class, HttpMessageNotReadableException.class, HttpMessageNotWritableException.class, BindException.class })
效果:也就是上面的ResponseEntity类处理异常的效果
默认响应错误的json。状态码 405
1 2 3 4 5 6 7 8 { "timestamp": "2023-04-18T11:13:05.515+00:00", "status": 405, "error": "Method Not Allowed", "trace": "org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' is not supported\r\n\tat org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.handleNoMatch(RequestMappingInfoHandlerMapping.java:265)\r\n\tat org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.lookupHandlerMethod(AbstractHandlerMethodMapping.java:441)\r\n\tat org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:382)\r\n\tat org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.getHandlerInternal(RequestMappingInfoHandlerMapping.java:126)\r\n\tat org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.getHandlerInternal(RequestMappingInfoHandlerMapping.java:68)\r\n\tat org.springframework.web.servlet.handler.AbstractHandlerMapping.getHandler(AbstractHandlerMapping.java:505)\r\n\tat org.springframework.web.servlet.DispatcherServlet.getHandler(DispatcherServlet.java:1275)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1057)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:974)\r\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1011)\r\n\tat org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914)\r\n\tat jakarta.servlet.http.HttpServlet.service(HttpServlet.java:563)\r\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)\r\n\tat jakarta.servlet.http.HttpServlet.service(HttpServlet.java:631)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:205)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)\r\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)\r\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)\r\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)\r\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)\r\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:166)\r\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)\r\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:493)\r\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115)\r\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)\r\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)\r\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:341)\r\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:390)\r\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)\r\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:894)\r\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741)\r\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)\r\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)\r\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)\r\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\r\n\tat java.base/java.lang.Thread.run(Thread.java:833)\r\n", "message": "Method 'POST' is not supported.", "path": "/list" }
开启ProblemDetails返回, 使用新的MediaType
Content-Type: application/problem+json+ 额外扩展返回
**
1 2 3 4 5 6 7 { "type": "about:blank", "title": "Method Not Allowed", "status": 405, "detail": "Method 'POST' is not supported.", "instance": "/list" }
2. 函数式Web
SpringMVC 5.2 以后 允许我们使用函数式的方式,定义Web的请求处理流程。
函数式接口
Web请求处理的方式:
@Controller + @RequestMapping:耦合式 (路由、业务耦合)
函数式Web:分离式(路由、业务分离)
1. 场景
场景:User RESTful - CRUD
GET /user/1 获取1号用户
GET /users 获取所有用户
POST /user ** 请求体**携带JSON,新增一个用户
PUT /user/1 请求体 携带JSON,修改1号用户
DELETE /user/1** 删除**1号用户
2. 核心类
RouterFunction
RequestPredicate
ServerRequest
ServerResponse
3. 示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.MediaType; import org.springframework.web.servlet.function.RequestPredicate; import org.springframework.web.servlet.function.RouterFunction; import org.springframework.web.servlet.function.ServerResponse; import static org.springframework.web.servlet.function.RequestPredicates.accept; import static org.springframework.web.servlet.function.RouterFunctions.route; @Configuration(proxyBeanMethods = false) public class MyRoutingConfiguration { private static final RequestPredicate ACCEPT_JSON = accept(MediaType.APPLICATION_JSON); @Bean public RouterFunction<ServerResponse> routerFunction(MyUserHandler userHandler) { return route() .GET("/{user}", ACCEPT_JSON, userHandler::getUser) .GET("/{user}/customers", ACCEPT_JSON, userHandler::getUserCustomers) .DELETE("/{user}", ACCEPT_JSON, userHandler::deleteUser) .build(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import org.springframework.stereotype.Component; import org.springframework.web.servlet.function.ServerRequest; import org.springframework.web.servlet.function.ServerResponse; @Component public class MyUserHandler { public ServerResponse getUser(ServerRequest request) { ... return ServerResponse.ok().build(); } public ServerResponse getUserCustomers(ServerRequest request) { ... return ServerResponse.ok().build(); } public ServerResponse deleteUser(ServerRequest request) { ... return ServerResponse.ok().build(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 package com.atguigu.web.config; import com.atguigu.web.bean.Person; import com.atguigu.web.biz.UserBizHandler; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.MediaType; import org.springframework.web.servlet.function.RequestPredicates; import org.springframework.web.servlet.function.RouterFunction; import org.springframework.web.servlet.function.RouterFunctions; import org.springframework.web.servlet.function.ServerResponse; /** * @author lfy * @Description * @create 2023-04-18 21:46 */ /** * 场景:User RESTful - CRUD * ● GET /user/1 获取1号用户 * ● GET /users 获取所有用户 * ● POST /user 请求体携带JSON,新增一个用户 * ● PUT /user/1 请求体携带JSON,修改1号用户 * ● DELETE /user/1 删除1号用户 */ @Configuration public class WebFunctionConfig { /** * 函数式Web: * 1、给容器中放一个Bean:类型是 RouterFunction<ServerResponse>,集中所有路由信息 * 2、每个业务准备一个自己的Handler * * * 核心四大对象 * 1、RouterFunction: 定义路由信息。发什么请求,谁来处理 * 2、RequestPredicate:定义请求规则:请求谓语。请求方式(GET、POST)、请求参数 * 3、ServerRequest: 封装请求完整数据 * 4、ServerResponse: 封装响应完整数据 */ @Bean//如果修饰在方法上,那么其方法的参数是从容器中获取的 public RouterFunction<ServerResponse> userRoute(UserBizHandler userBizHandler/*这个会被自动注入进来*/){ return RouterFunctions.route() //开始定义路由信息 .GET("/user/{id}", RequestPredicates.accept(MediaType.ALL), userBizHandler::getUser) .GET("/users", userBizHandler::getUsers) .POST("/user", RequestPredicates.accept(MediaType.APPLICATION_JSON), userBizHandler::saveUser) .PUT("/user/{id}", RequestPredicates.accept(MediaType.APPLICATION_JSON), userBizHandler::updateUser) .DELETE("/user/{id}", userBizHandler::deleteUser) .build(); } // @Bean // public RouterFunction<ServerResponse> groupRoute(UserBizHandler userBizHandler/*这个会被自动注入进来*/){ // // return RouterFunctions.route() //开始定义路由信息 // .GET("/user/{id}", RequestPredicates.accept(MediaType.ALL), userBizHandler::getUser) // .GET("/users", userBizHandler::getUsers) // .POST("/user", RequestPredicates.accept(MediaType.APPLICATION_JSON), userBizHandler::saveUser) // .PUT("/user/{id}", RequestPredicates.accept(MediaType.APPLICATION_JSON), userBizHandler::updateUser) // .DELETE("/user/{id}", userBizHandler::deleteUser) // .build(); // } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 package com.atguigu.web.biz; import com.atguigu.web.bean.Person; import jakarta.servlet.ServletException; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import org.springframework.web.servlet.function.ServerRequest; import org.springframework.web.servlet.function.ServerResponse; import java.io.IOException; import java.util.Arrays; import java.util.List; /** * @author lfy * @Description 专门处理User有关的业务 * @create 2023-04-18 21:55 */ @Slf4j @Service public class UserBizHandler { /** * 查询指定id的用户 * @param request * @return */ public ServerResponse getUser(ServerRequest request) throws Exception{ String id = request.pathVariable("id"); log.info("查询 【{}】 用户信息,数据库正在检索",id); //业务处理 Person person = new Person(1L,"哈哈","aa@qq.com",18,"admin"); //构造响应 return ServerResponse .ok() .body(person); } /** * 获取所有用户 * @param request * @return * @throws Exception */ public ServerResponse getUsers(ServerRequest request) throws Exception{ log.info("查询所有用户信息完成"); //业务处理 List<Person> list = Arrays.asList(new Person(1L, "哈哈", "aa@qq.com", 18, "admin"), new Person(2L, "哈哈2", "aa2@qq.com", 12, "admin2")); //构造响应 return ServerResponse .ok() .body(list); //凡是body中的对象,就是以前@ResponseBody原理。利用HttpMessageConverter 写出为json } /** * 保存用户 * @param request * @return */ public ServerResponse saveUser(ServerRequest request) throws ServletException, IOException { //提取请求体 Person body = request.body(Person.class); log.info("保存用户信息:{}",body); return ServerResponse.ok().build(); } /** * 更新用户 * @param request * @return */ public ServerResponse updateUser(ServerRequest request) throws ServletException, IOException { Person body = request.body(Person.class); log.info("保存用户信息更新: {}",body); return ServerResponse.ok().build(); } /** * 删除用户 * @param request * @return */ public ServerResponse deleteUser(ServerRequest request) { String id = request.pathVariable("id"); log.info("删除【{}】用户信息",id); return ServerResponse.ok().build(); } }
3、SpringBoot3-数据访问
整合SSM场景SpringBoot 整合 Spring、SpringMVC、MyBatis 进行数据访问场景开发
**创建SSM项目(在创建springboot的时候勾选要导入的包):
**
1 2 3 4 5 6 7 8 9 10 11 <!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>3.0.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>
2. 配置数据源
1 2 3 4 5 spring.datasource.url=jdbc:mysql://192.168.200.100:3306/demo spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.username=root spring.datasource.password=123456 spring.datasource.type=com.zaxxer.hikari.HikariDataSource
安装MyBatisX 插件,帮我们生成Mapper接口的xml文件即可。MyBatisX的使用:
选中接口名->点击右键->选择显示上下文操作->选择MybatisX的功能
或者选中接口名Altet+Enter
在接口下的方法使用Alter+Enter可以生成相应的sql语句 片段
3. 配置MyBatis
1 2 3 4 #指定mapper映射文件位置 mybatis.mapper-locations=classpath:/mapper/*.xml #参数项调整,也就是可以把数据库表中的下划线命名的属性转换为驼峰命名 mybatis.configuration.map-underscore-to-camel-case=true
4. CRUD编写
CRUD 是一个常用的术语,用于描述对于持久化数据的基本操作。CRUD 是 Create(创建)、Read(读取)、Update(更新)和Delete(删除)的缩写。
编写Bean
编写Mapper
使用mybatisx插件,快速生成MapperXML
测试CRUD
总结:
1. @Data//生成get和set方法
2.controller类下的
@Autowired//下面的提示的来源是因为这个时候,UserMapper中的接口没有注入到容器中,是springboot启动利用@MapperScan才将该接口注入到容器中的
UserMapper userMapper**;3.mapper接口中的 每个方法都在Mapper文件中有一个sql标签对应。所有参数都应该用@Param进行签名,以后使用指定的名字在SQL中取值
4.mapper.xml文件中的标签namespace属性也就是全类名接口的全类名和namespace的值是一一对应的
5.使用mybatis对数据库进行操作的时候,在spriongboot启动类的前要添加MapperScan(basePackages = “com.atguigu.boot3.ssm.mapper”)
_1、@MapperScan【批量扫描注解】; 告诉MyBatis,扫描哪个包下面的所有接口
** @MapperScan _**_是 MyBatis-Spring 框架提供的注解,用于扫描指定包下的 Mapper 接口,并将其注册为 Spring 的 Bean。
这样就可以在controller中使用自动注入
2、在配置文件中使用mybatis.mapper-locations,告诉MyBatis,每个接口的xml文件都在哪里_
5. 自动配置原理
SSM整合总结:
导入 mybatis-spring-boot-starter
配置数据源信息
配置mybatis的mapper接口扫描与xml映射文件扫描
编写bean,mapper,生成xml,编写sql 进行crud。事务等操作依然和Spring中用法一样
效果:
所有sql写在xml中
所有mybatis配置写在application.properties下面
jdbc场景的自动配置:
mybatis-spring-boot-starter导入 spring-boot-starter-jdbc,jdbc是操作数据库的场景
Jdbc场景的几个自动配置()
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
数据源的自动配置
所有和数据源有关的配置都绑定在DataSourceProperties类中
默认使用 HikariDataSource。
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration
具有的底层能力:数据源、JdbcTemplate、事务
MyBatisAutoConfiguration:配置了MyBatis的整合流程
mybatis-spring-boot-starter导入 mybatis-spring-boot-autoconfigure(mybatis的自动配置包),
默认加载两个自动配置类:
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
必须在数据源配置好之后才配置
给容器中SqlSessionFactory组件。创建和数据库的一次会话
给容器中SqlSessionTemplate组件。操作数据库
MyBatis的所有配置绑定在 MybatisProperties
每个Mapper接口 的代理对象 是怎么创建放到容器中。详见**@MapperScan**原理:
利用@Import(MapperScannerRegistrar.class)批量给容器中注册组件。解析指定的包路径里面的每一个类,为每一个Mapper接口类,创建Bean定义信息,注册到容器中。
如何分析哪个场景导入以后,开启了哪些自动配置类。
找:classpath:/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中配置的所有值,就是要开启的自动配置类,但是每个类可能有条件注解,基于条件注解判断哪个自动配置类生效了。
6. 快速定位生效的配置
1 2 3 #开启调试模式,详细打印开启了哪些自动配置 debug=true # Positive(生效的自动配置) Negative(不生效的自动配置)
7. 扩展:整合其他数据源
1. Druid 数据源
暂不支持 SpringBoot3
导入druid-starter
写配置
分析自动配置了哪些东西,怎么用
Druid官网:https://github.com/alibaba/druid
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #数据源基本配置 spring.datasource.url=jdbc:mysql://192.168.200.100:3306/demo spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.username=root spring.datasource.password=123456 spring.datasource.type=com.alibaba.druid.pool.DruidDataSource # 配置StatFilter监控 spring.datasource.druid.filter.stat.enabled=true spring.datasource.druid.filter.stat.db-type=mysql spring.datasource.druid.filter.stat.log-slow-sql=true spring.datasource.druid.filter.stat.slow-sql-millis=2000 # 配置WallFilter防火墙 spring.datasource.druid.filter.wall.enabled=true spring.datasource.druid.filter.wall.db-type=mysql spring.datasource.druid.filter.wall.config.delete-allow=false spring.datasource.druid.filter.wall.config.drop-table-allow=false # 配置监控页,内置监控页面的首页是 /druid/index.html spring.datasource.druid.stat-view-servlet.enabled=true spring.datasource.druid.stat-view-servlet.login-username=admin spring.datasource.druid.stat-view-servlet.login-password=admin spring.datasource.druid.stat-view-servlet.allow=* # 其他 Filter 配置不再演示 # 目前为以下 Filter 提供了配置支持,请参考文档或者根据IDE提示(spring.datasource.druid.filter.*)进行配置。 # StatFilter # WallFilter # ConfigFilter # EncodingConvertFilter # Slf4jLogFilter # Log4jFilter # Log4j2Filter # CommonsLogFilter
附录:示例数据库(要先自己创建test数据库)
1 2 3 4 5 6 7 8 9 CREATE TABLE `t_user` ( `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '编号', `login_name` VARCHAR(200) NULL DEFAULT NULL COMMENT '用户名称' COLLATE 'utf8_general_ci', `nick_name` VARCHAR(200) NULL DEFAULT NULL COMMENT '用户昵称' COLLATE 'utf8_general_ci', `passwd` VARCHAR(200) NULL DEFAULT NULL COMMENT '用户密码' COLLATE 'utf8_general_ci', PRIMARY KEY (`id`) ); insert into t_user(login_name, nick_name, passwd) VALUES ('zhangsan','张三','123456');
4、SpringBoot3-基础特性
SpringApplication1.1. 自定义 banner类路径添加banner.txt或设置spring.banner.location就可以定制
这里是在配置文件中设置spring.banner.location=classpath:banner.txt然后在banner.txt文件中填充即可。
banner.txt中可以存放Ascll类型的文本,当然图片生成的Ascall类型的也是可以的
banner推荐网站:Spring Boot banner 在线生成工具,制作下载英文 banner.txt,修改替换 banner.txt 文字实现自定义,个性化启动 banner-bootschool.net
**Spring Boot 中有一个名为 “Banner” 的功能,它用于在应用程序启动时显示自定义的启动横幅。横幅是一个包含 ASCII 艺术、应用程序名称和版本号等信息的文本,它可以用于个性化应用程序的启动过程。
上面的横幅就是启动成功的这个:
1.2. 自定义 SpringApplication
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import org.springframework.boot.Banner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class MyApplication { public static void main(String[] args) { SpringApplication application = new SpringApplication(MyApplication.class); application.setBannerMode(Banner.Mode.OFF); application.run(args); } }
1.3. FluentBuilder API
1 2 3 4 5 new SpringApplicationBuilder() .sources(Parent.class) .child(Application.class) .bannerMode(Banner.Mode.OFF) .run(args);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 @SpringBootApplication //主程序类 public class Boot306FeaturesApplication { public static void main(String[] args) { //1、SpringApplication: Boot应用的核心API入口 // SpringApplication.run(Boot306FeaturesApplication.class, args); // args是springboot应用启动的核心参数 //===============1、自定义 SpringApplication 的底层设置,也就是把SpringApplication.run(Boot306FeaturesApplication.class, args); // 分开,生成SpringApplication类,然后根据SpringApplication类来自定义启动的相关设置 // SpringApplication application = new SpringApplication(Boot306FeaturesApplication.class); // // //程序化调整【SpringApplication的参数】 //// application.setDefaultProperties(); // //这个配置不优先,作用是将banner的打印关闭。如果配置文件有类似的配置,配置文件优先 // application.setBannerMode(Banner.Mode.OFF); // // // //【配置文件优先级高于程序化调整的优先级】 // // //2、SpringApplication 运行起来 // application.run(args); //================2、Builder方式构建 SpringApplication; 通过FluentAPI进行设置 ConfigurableApplicationContext context = new SpringApplicationBuilder() .main(Boot306FeaturesApplication.class) //各种参数均可设置 .sources(Boot306FeaturesApplication.class) .bannerMode(Banner.Mode.CONSOLE) .properties("server.port=8888","aaa=bbb") //springboot所有配置项都可以在这里定义 // .environment(null) // .listeners(null) .run(args); try { Cat cat = context.getBean(Cat.class); log.info("组件cat:{}",cat); }catch (Exception e){ } try { Dog dog = context.getBean(Dog.class); log.info("组件dog:{}",dog); }catch (Exception e){ } try { Pig pig = context.getBean(Pig.class); log.info("组件pig:{}",pig); }catch (Exception e){ } try { Sheep sheep = context.getBean(Sheep.class); log.info("组件sheep:{}",sheep); }catch (Exception e){ } } }
1 2 3 4 #指定banner文本的路径 spring.banner.location=classpath:banner.txt #关闭banner模式 spring.main.banner-mode=off
2. Profiles
环境隔离能力;快速切换开发、测试、生产环境(最好的例子就是在开发,测试,生产所需要的数据库不一样,这个时候就需要自动切换数据库)
步骤:
标识环境 :指定哪些组件、配置在哪个环境生效
切换环境 :这个环境对应的所有组件和配置就应该生效
2.1. 使用
2.1.1 指定环境
Spring Profiles 提供一种隔离配置 的方式,使其仅在特定环境 生效;
任何@Component, @Configuration 或 @ConfigurationProperties 可以使用 @Profile 标记,来指定何时被加载。【容器中的组件 都可以被 @Profile标记】
2.1.2 环境激活(也就是指定当前系统运行的特定环境)
配置激活指定环境; 配置文件
spring.profiles.active=production,hsqldb
也可以使用命令行激活。–spring.profiles.active=dev,hsqldb
还可以配置默认环境 ; 不标注@Profile 的组件永远都存在。
以前默认环境叫default
spring.profiles.default=test修改默认环境是test,变形激活了test环境
推荐使用激活方式激活指定环境
2.1.3 环境包含
注意:
spring.profiles.active 和spring.profiles.default 只能用到** 无 profile 的文件中,如果在application-dev.yaml中编写就是 无效的**
也可以额外添加生效文件,而不是激活替换。比如:
1 2 spring.profiles.include[0]=common spring.profiles.include[1]=local
最佳实战:
**生效的环境 **= 激活的环境/默认环境 + 包含的环境
项目里面这么用
基础的配置mybatis、log、xxx:写到包含环境中
需要动态切换变化的 db、redis:写到激活的环境中
2.2. Profile 分组
创建prod组,指定包含db和mq配置
1 2 spring.profiles.group.prod[0]=db spring.profiles.group.prod[1]=mq
使用–spring.profiles.active=prod ,就会激活prod,db,mq配置文件
2.3. Profile 配置文件
application-{profile}.properties可以作为指定环境的配置文件 。
激活这个环境,配置 就会生效。最终生效的所有配置 是
application.properties:主配置文件,任意时候都生效
application-{profile}.properties:指定环境配置文件,激活指定环境生效
profile优先级 > application
3. 外部化配置
场景 :线上应用如何快速修改配置 ,并应用最新配置 ?
SpringBoot 使用 配置优先级 + 外部配置 简化配置更新、简化运维。
只需要给jar应用所在的文件夹放一个application.properties最新配置文件,重启项目就能自动应用最新配置
3.1. 配置优先级
Spring Boot 允许将配置外部化 ,以便可以在不同的环境中使用相同的应用程序代码。
我们可以使用各种外部配置源 ,包括Java Properties文件、YAML文件、环境变量和命令行参数。
@Value可以获取值,也可以用@ConfigurationProperties将所有属性绑定到java object中
以下是 SpringBoot 属性源加载顺序。后面的会覆盖前面的值 。由低到高,高优先级配置覆盖低优先级
默认属性 (通过SpringApplication.setDefaultProperties指定的)
@PropertySource指定加载的配置(需要写在@Configuration类上才可生效),其中指定加载 类的文件一般是配置文件
配置文件(application.properties/yml等)
RandomValuePropertySource支持的random.*配置(如:@Value(“${random.int}”))
OS 环境变量
Java 系统属性(System.getProperties())
JNDI 属性(来自java:comp/env)
ServletContext初始化参数
ServletConfig初始化参数
SPRING_APPLICATION_JSON属性(内置在环境变量或系统属性中的 JSON)
命令行参数
测试属性。(@SpringBootTest进行测试时指定的属性)
测试类@TestPropertySource注解
Devtools 设置的全局属性。($HOME/.config/spring-boot)
结论:配置可以写到很多位置,常见的优先级顺序:
命令行> 配置文件> springapplication配置(可以这样理解程序运行的时候,先运行springboot启动程序的默认配置,然后读取配置文件,然后是命令行运行。相当于后面的配置信息会覆盖前面的,可以这样理解但是实际上并非是这样的)
配置文件优先级 如下:(后面覆盖前面 )
jar 包内 的application.properties/yml
jar 包内 的application-{profile}.properties/yml
jar 包外 的application.properties/yml
jar 包外 的application-{profile}.properties/yml
建议 :用一种格式的配置文件 。如果.properties和.yml同时存在,则.properties优先
结论:包外 > 包内; 同级情况:profile配置 > application配置
所有参数均可由命令行传入,使用–参数项=参数值,将会被添加到环境变量中,并优先于配置文件。
比如java -jar app.jar --name=“Spring”,可以使用@Value(“${name}”)获取
演示场景:
包内: application.properties server.port=8000
包内: application-dev.properties server.port=9000
包外: application.properties server.port=8001
包外: application-dev.properties server.port=9001
启动端口?:命令行 > 9001 > 8001 > 9000 > 8000
3.2. 外部配置
SpringBoot 应用启动时会自动寻找application.properties和application.yaml位置,进行加载。顺序如下:(后面覆盖前面 )
类路径: 内部
类根路径
类下/config包
当前路径(项目所在的位置)
当前路径
当前下/config子目录
/config目录的直接子目录
最终效果:优先级由高到低,前面覆盖后面
命令行 > 包外config直接子目录 > 包外config目录 > 包外根目录 > 包内目录
同级比较:
profile配置 > 默认配置
properties配置 > yaml配置
**规律:最外层的最优先。
命令行 > 所有
包外 > 包内
config目录 > 根目录
**profile > application **
配置不同就都生效(互补),配置相同高优先级覆盖低优先级
3.3. 导入配置
使用spring.config.import可以导入额外配置:
导入配置的优先级低于配置文件的优先级。
1 2 spring.config.import=my.properties my.property=value
无论以上写法的先后顺序,my.properties的值总是优先于直接在文件中编写的my.property。
3.4. 属性占位符
配置文件中可以使用 ${name:default}形式取出之前配置过的值,${name:default}这里的意思是配置文件中有name值,就采用该值,没有的换就让name的值为default的值。@Value可以获取配置文件中的值,@Value(${name:hahha}) String name表示如果配置文件没有name的配置那么name的值为haha,如果有name的值就是配置文件中的值,
1 2 3 @Value注解是Spring框架提供的一种方式,可以从外部的配置文件、系统属性或表达式中获取值, 并赋给Bean中的字段或参数。@Value注解的参数是一个字符串,可以是一个普通的字符串,也可以是一个占 位符或表达式,用$ {}或# {}包围。
1 2 app.name=MyApp app.description=${app.name} is a Spring Boot application written by ${username:Unknown}
4. 单元测试-JUnit5
4.1. 整合
SpringBoot 提供一系列测试工具集及注解方便我们进行测试。
spring-boot-test提供核心测试能力,spring-boot-test-autoconfigure提供测试的一些自动配置。
我们只需要导入spring-boot-starter-test即可整合测试
1 2 3 4 5 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
spring-boot-starter-test默认提供了以下库供我们测试使用
4.2. 测试
1 2 3 4 5 1.@SpringBootTest //具备测试SpringBoot应用容器中所有组件的功能,测试的时候会启动spingboot //测试类也必须在主程序所在的包及其子包;这里的主程序是springbootApplication修饰的类,之所以 测试类必须在主程序所在的包及其子包的原因是:SpringBoot的默认扫描规则,只扫描自己主程序所在的包以 及子包,这样测试类的注解才能被扫描到。
4.2.0 组件测试
直接@Autowired容器中的组件进行测试
JUnit5的注解与JUnit4的注解有所变化
https://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations
@DisplayName(“测试1”)//自定义测试名字
4.2.1 注解
**@Test :**表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
**@ParameterizedTest :**表示方法是参数化测试,下方会有详细介绍
**@RepeatedTest :**表示方法可重复执行,下方会有详细介绍
**@DisplayName :**为测试类或者测试方法设置展示名称
**@BeforeEach :**表示在每个单元测试之前执行
**@AfterEach :**表示在每个单元测试之后执行
**@BeforeAll :**表示在所有单元测试之前执行
**@AfterAll :**表示在所有单元测试之后执行
**@Tag :**表示单元测试类别,类似于JUnit4中的@Categories
**@Disabled :**表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
**@Timeout :**表示测试方法运行如果超过了指定时间将会返回错误
**@ExtendWith :**为测试类或测试方法提供扩展类引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; class StandardTests { @BeforeAll static void initAll() { } @BeforeEach void init() { } @DisplayName("😱") @Test void succeedingTest() { } @Test void failingTest() { fail("a failing test"); } @Test @Disabled("for demonstration purposes") void skippedTest() { // not executed } @Test void abortedTest() { assumeTrue("abc".contains("Z")); fail("test should have been aborted"); } @AfterEach void tearDown() { } @AfterAll static void tearDownAll() { } }
4.2.2 断言
4.2.3 嵌套测试
JUnit 5 可以通过 Java 中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach 和@AfterEach 注解,而且嵌套的层次没有限制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 @DisplayName("A stack") class TestingAStackDemo { Stack<Object> stack; @Test @DisplayName("is instantiated with new Stack()") void isInstantiatedWithNew() { new Stack<>(); } @Nested @DisplayName("when new") class WhenNew { @BeforeEach void createNewStack() { stack = new Stack<>(); } @Test @DisplayName("is empty") void isEmpty() { assertTrue(stack.isEmpty()); } @Test @DisplayName("throws EmptyStackException when popped") void throwsExceptionWhenPopped() { assertThrows(EmptyStackException.class, stack::pop); } @Test @DisplayName("throws EmptyStackException when peeked") void throwsExceptionWhenPeeked() { assertThrows(EmptyStackException.class, stack::peek); } @Nested @DisplayName("after pushing an element") class AfterPushing { String anElement = "an element"; @BeforeEach void pushAnElement() { stack.push(anElement); } @Test @DisplayName("it is no longer empty") void isNotEmpty() { assertFalse(stack.isEmpty()); } @Test @DisplayName("returns the element when popped and is empty") void returnElementWhenPopped() { assertEquals(anElement, stack.pop()); assertTrue(stack.isEmpty()); } @Test @DisplayName("returns the element when peeked but remains not empty") void returnElementWhenPeeked() { assertEquals(anElement, stack.peek()); assertFalse(stack.isEmpty()); } } } }
4.2.4 参数化测试
参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利。
**
利用**@ValueSource**等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。
@ValueSource : 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
@NullSource : 表示为参数化测试提供一个null的入参
@EnumSource : 表示为参数化测试提供一个枚举入参
@CsvFileSource :表示读取指定CSV文件内容作为参数化测试入参
@MethodSource :表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @ParameterizedTest @ValueSource(strings = {"one", "two", "three"}) @DisplayName("参数化测试1") public void parameterizedTest1(String string) { System.out.println(string); Assertions.assertTrue(StringUtils.isNotBlank(string)); } @ParameterizedTest @MethodSource("method") //指定方法名 @DisplayName("方法来源参数") public void testWithExplicitLocalMethodSource(String name) { System.out.println(name); Assertions.assertNotNull(name); } static Stream<String> method() { return Stream.of("apple", "banana"); }
5、SpringBoot3-核心原理
1. 事件和监听器
1. 生命周期监听场景:监听应用的 生命周期
1. 监听器-SpringApplicationRunListener
自定义SpringApplicationRunListener来监听事件;编写SpringApplicationRunListener实现类在 META-INF/spring.factories 中配置 org.springframework.boot.SpringApplicationRunListener=自己的Listener,还可以指定一个有参构造器,接受两个参数(SpringApplication application, String[] args)springboot 在spring-boot.jar中配置了默认的 Listener,如下
**
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 /** * Listener先要从 META-INF/spring.factories 读到 * * 1、引导: 利用 BootstrapContext 引导整个项目启动 * starting: 应用开始,SpringApplication的run方法一调用,只要有了 BootstrapContext 就执行 * environmentPrepared: 环境准备好(把启动参数等绑定到环境变量中),但是ioc还没有创建;【调一次】 * 2、启动: * contextPrepared: ioc容器创建并准备好,但是sources(主配置类)没加载。并关闭引导上下文;组件都没创建 【调一次】 * contextLoaded: ioc容器加载。主配置类加载进去了。但是ioc容器还没刷新(我们的bean没创建)。 * =======截止以前,ioc容器里面还没造bean呢======= * started: ioc容器刷新了(所有bean造好了),但是 runner 没调用。 * ready: ioc容器刷新了(所有bean造好了),所有 runner 调用完了。 * 3、运行 * 以前步骤都正确执行,代表容器running。 */
2. 生命周期全流程
1 2 3 4 5 在Spring Boot中,Runner是一个接口,用于在Spring应用程序启动完成后执行一些特定的任务。 它是Spring Boot提供的一种扩展机制,允许开发人员在应用程序启动后立即执行一些代码逻辑。 开发人员可以通过实现Runner接口并注入到Spring应用程序中,从而在应用程序启动后执行自定义的任务。 runner是springboot底层用来感知生命周期特定阶段的一个回调。 不同于SpringApplicationRunListener,这个是用来感知全生命周期的
2. 事件触发时机
1. 各种回调监听器
BootstrapRegistryInitializer: ** 感知特定阶段:感知 引导初始化**
META-INF/spring.factories
创建引导上下文bootstrapContext的时候触发。
application.addBootstrapRegistryInitializer();
场景:进行密钥校对授权。
ApplicationContextInitializer: ** 感知特定阶段:** 感知ioc容器初始化
META-INF/spring.factories
application.addInitializers();
ApplicationListener: 感知全阶段:基于事件机制,感知事件。 一旦到了哪个阶段可以做别的事
@Bean或@EventListener: 事件驱动
SpringApplication.addListeners(…)或 SpringApplicationBuilder.listeners(…)
META-INF/spring.factories
SpringApplicationRunListener: 感知全阶段生命周期 + 各种阶段都能自定义操作; 功能更完善。
META-INF/spring.factories
ApplicationRunner: 感知特定阶段:感知应用就绪Ready。卡死应用,就不会就绪
CommandLineRunner: 感知特定阶段:感知应用就绪Ready。卡死应用,就不会就绪
最佳实战:
如果项目启动前做事: BootstrapRegistryInitializer 和 ApplicationContextInitializer
如果想要在项目启动完成后做事:ApplicationRunner和 CommandLineRunner
如果要干涉生命周期做事:SpringApplicationRunListener
如果想要用事件机制:ApplicationListener
上面的都是接口,可以通过实现接口,然后在 META-INF/spring.factories配置文件中填充实现上述接口的类,就可以实现在生命周期的特定阶段完成某项任务!
2. 完整触发流程
9大事件 触发顺序&时机
ApplicationStartingEvent:应用启动但未做任何事情, 除过注册listeners and initializers.
ApplicationEnvironmentPreparedEvent: Environment 准备好,但context 未创建.
ApplicationContextInitializedEvent: ApplicationContext 准备好,ApplicationContextInitializers 调用,但是任何bean未加载
ApplicationPreparedEvent: 容器刷新之前,bean定义信息加载
ApplicationStartedEvent: 容器刷新完成, runner未调用
=========以下就开始插入了探针机制 ============
AvailabilityChangeEvent: LivenessState.CORRECT应用存活; 存活探针
ApplicationReadyEvent: 任何runner被调用
AvailabilityChangeEvent:ReadinessState.ACCEPTING_TRAFFIC就绪探针 ,可以接请求
ApplicationFailedEvent:启动出错
应用事件发送顺序如下:
感知应用是否存活了:可能植物状态,虽然活着但是不能处理请求。
应用是否 就绪**了:能响应请求,说明确实活的比较好。
3. SpringBoot 事件驱动开发
应用启动过程生命周期事件感知(9大事件) 、应用运行中事件感知(无数种) 。
事件发布 :ApplicationEventPublisherAware或注入:ApplicationEventMulticaster
事件监听 :组件 + @EventListener
事件发布者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Service public class EventPublisher implements ApplicationEventPublisherAware { /** * 底层发送事件用的组件,SpringBoot会通过ApplicationEventPublisherAware接口自动注入给我们 * 事件是广播出去的。所有监听这个事件的监听器都可以收到 */ ApplicationEventPublisher applicationEventPublisher; /** * 所有事件都可以发 * @param event */ public void sendEvent(ApplicationEvent event) { //调用底层API发送事件 applicationEventPublisher.publishEvent(event); } /** * 会被自动调用,把真正发事件的底层组组件给我们注入进来 * @param applicationEventPublisher event publisher to be used by this object */ @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.applicationEventPublisher = applicationEventPublisher; } }
事件订阅者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Service public class CouponService { @Order(1) @EventListener public void onEvent(LoginSuccessEvent loginSuccessEvent){ System.out.println("===== CouponService ====感知到事件"+loginSuccessEvent); UserEntity source = (UserEntity) loginSuccessEvent.getSource(); sendCoupon(source.getUsername()); } public void sendCoupon(String username){ System.out.println(username + " 随机得到了一张优惠券"); } }
**2. **自动配置原理
1. 入门理解
应用关注的三大核心:场景、配置、组件
1. 自动配置流程
**
导入starter
依赖导入autoconfigure
寻找类路径下 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件
启动,加载所有 自动配置类xxxAutoConfiguration
给容器中配置功能组件
组件参数绑定到 属性类中。xxxProperties
属性类和配置文件前缀项绑定
@Contional派生的条件注解进行判断是否组件生效
效果:
修改配置文件,修改底层参数
所有场景自动配置好直接使用
可以注入SpringBoot配置好的组件随时使用
2. SPI机制
Java中的SPI(Service Provider Interface)是一种软件设计模式,用于在应用程序中动态地发现和加载组件。SPI的思想 是,定义一个接口或抽象类,然后通过在classpath中定义实现该接口的类来实现对组件的动态发现和加载。
SPI的主要目的是解决在应用程序中使用可插拔组件的问题。例如,一个应用程序可能需要使用不同的日志框架或数据库连接池,但是这些组件的选择可能取决于运行时的条件。通过使用SPI,应用程序可以在运行时发现并加载适当的组件,而无需在代码中硬编码这些组件的实现类。
在Java中,SPI 的实现方式是通过在META-INF/services目录下创建一个以服务接口全限定名为名字的文件,文件中包含实现该服务接口的类的全限定名。当应用程序启动时,Java的SPI机制会自动扫描classpath中的这些文件,并根据文件中指定的类名来加载实现类。
通过使用SPI,应用程序可以实现更灵活、可扩展的架构,同时也可以避免硬编码依赖关系和增加代码的可维护性。
以上回答来自ChatGPT-3.5
在SpringBoot中,META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
作业:写一段java的spi机制代码
3. 功能开关
自动配置:全部都配置好,什么都不用管。 自动批量导入
@EnableXxxx:手动控制哪些功能的开启; 手动导入。
2. 进阶理解
1. @SpringBootApplication
@SpringBootConfiguration
就是: @Configuration ,容器中的组件,配置类。spring ioc启动就会加载创建这个类对象
@EnableAutoConfiguration:开启自动配置
开启自动配置
@AutoConfigurationPackage:扫描主程序包:加载自己的组件
利用 @Import(AutoConfigurationPackages.Registrar.class) 想要给容器中导入组件。
把主程序所在的包的所有组件导入进来。
为什么SpringBoot默认只扫描主程序所在的包及其子包
**
@Import(AutoConfigurationImportSelector.class):加载所有自动配置类:加载starter导入的组件
1 2 List<String> configurations = ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()) .getCandidates();
**扫描SPI文件:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
**
@ComponentScan
组件扫描:排除一些组件(哪些不要)
排除前面已经扫描进来的配置类、和自动配置类。
1 2 @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
2. 完整启动加载流程
生命周期启动加载流程
3. 自定义starter
场景:抽取聊天机器人场景,它可以打招呼。
效果:任何项目导入此starter都具有打招呼功能,并且问候语中的人名需要可以在配置文件中修改
创建自定义starter项目,引入spring-boot-starter基础依赖
编写模块功能,引入模块所有需要的依赖。
编写xxxAutoConfiguration自动配置类,帮其他项目导入这个模块需要的所有组件
编写配置文件META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports指定启动需要加载的自动配置
1. 业务代码
自定义配置有提示。导入以下依赖重启项目,再写配置文件就有提示**
1 2 3 4 5 6 7 8 9 10 @ConfigurationProperties(prefix = "robot") //此属性类和配置文件指定前缀绑定 @Component @Data public class RobotProperties { private String name; private String age; private String email; }
1 2 3 4 5 6 <!-- 导入配置处理器,配置文件自定义的properties配置都会有提示--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>
2. 基本抽取
创建starter项目,把公共代码需要的所有依赖导入
把公共代码复制进来
自己写一个 RobotAutoConfiguration,给容器中导入这个场景需要的所有组件
为什么这些组件默认不会扫描进去?
starter所在的包和 引入它的项目的主程序所在的包不是父子层级
别人引用这个starter,直接导入这个 RobotAutoConfiguration,就能把这个场景的组件导入进来
功能生效。
测试编写配置文件
3. 使用@EnableXxx机制
1 2 3 4 5 6 7 8 @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented @Import(RobotAutoConfiguration.class) public @interface EnableRobot { }
****别人引入starter需要使用 @EnableRobot开启功能 **
4. 完全自动配置
依赖SpringBoot的SPI机制
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中编写好我们自动配置类的全类名即可
项目启动,自动加载我们的自动配置类
SpringBoot3-场景整合
0. 云服务器
重要:开通云服务器以后,请一定在安全组设置规则,放行端口
1. Docker安装
还不会docker的同学,参考【云原生实战(10~25集)】快速入门
https://www.bilibili.com/video/BV13Q4y1C7hS?p=10
1 2 3 4 5 6 7 8 9 10 11 12 13 14 sudo yum install -y yum-utils sudo yum-config-manager \ --add-repo \ https://download.docker.com/linux/centos/docker-ce.repo sudo yum install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin sudo systemctl enable docker --now #测试工作 docker ps # 批量安装所有软件 docker compose
创建 /prod 文件夹,准备以下文件
2. prometheus.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 global: scrape_interval: 15s evaluation_interval: 15s scrape_configs: - job_name: 'prometheus' static_configs: - targets: ['localhost:9090'] - job_name: 'redis' static_configs: - targets: ['redis:6379'] - job_name: 'kafka' static_configs: - targets: ['kafka:9092']
3. docker-compose.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 version: '3.9' services: redis: image: redis:latest container_name: redis restart: always ports: - "6379:6379" networks: - backend zookeeper: image: bitnami/zookeeper:latest container_name: zookeeper restart: always environment: ZOOKEEPER_CLIENT_PORT: 2181 ZOOKEEPER_TICK_TIME: 2000 networks: - backend kafka: image: bitnami/kafka:3.4.0 container_name: kafka restart: always depends_on: - zookeeper ports: - "9092:9092" environment: ALLOW_PLAINTEXT_LISTENER: yes KAFKA_CFG_ZOOKEEPER_CONNECT: zookeeper:2181 KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 networks: - backend kafka-ui: image: provectuslabs/kafka-ui:latest container_name: kafka-ui restart: always depends_on: - kafka ports: - "8080:8080" environment: KAFKA_CLUSTERS_0_NAME: dev KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:9092 networks: - backend prometheus: image: prom/prometheus:latest container_name: prometheus restart: always volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml ports: - "9090:9090" networks: - backend grafana: image: grafana/grafana:latest container_name: grafana restart: always depends_on: - prometheus ports: - "3000:3000" networks: - backend networks: backend: name: backend
4. 启动环境
1 docker compose -f docker-compose.yml up -d
5. 验证:
如果通过浏览器验证没有通过的话,原因可能是没有配置安全组:例如Kafka-ui通过浏览器无法访问!
添加安全组8080,这里的来源是:可以设置为 0.0.0.0/0,表示允许来自任何 IP 地址的流量,或者您可以指定特定的 IP 地址范围或单个 IP 地址。
Redis:你的ip:6379
Kafka:你的ip:9092
Prometheus:你的ip:9090
Grafana:你的ip:3000
Docker:(10-25)
Docker基本概念
1、解决的问题:
1、统一标准
应用构建
Java、C++、JavaScript
打成软件包
.exe
docker build … 镜像
应用分享:
所有软件的镜像放到一个指定地方 docker hub
安卓,应用市场应用运行统一标准的镜像docker run
…
2、资源隔离
cpu、memory资源隔离与限制
访问设备隔离与限制
网络隔离与限制
用户、用户组隔离限制
…
2、架构
Docker_Host:
Docker Daemon:
Client:
Registry:
Images:
Containers:
交互逻辑
装好Docker ,然后去 软件市场(doucker hub) 寻找镜像 ,下载并运行,查看容器 状态日志等排错
3、安装
1、centos下安装docker
其他系统参照如下文档
https://docs.docker.com/engine/install/centos/
1、移除以前docker相关包
1 2 3 4 5 6 7 8 sudo yum remove docker \ docker-client \ docker-client-latest \ docker-common \ docker-latest \ docker-latest-logrotate \ docker-logrotate \ docker-engine
2、配置yum源并且添加镜像仓库:
1 2 3 4 5 sudo yum install -y yum-utils sudo yum-config-manager \ --add-repo \ http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
3、安装docker
1 2 3 4 5 sudo yum install -y docker-ce docker-ce-cli containerd.io #以下是在安装k8s的时候使用 yum install -y docker-ce-20.10.7 docker-ce-cli-20.10.7 containerd.io-1.4.6
4、启动
1 systemctl enable docker --now
5、配置加速
这里额外添加了docker的生产环境核心配置cgroup:
其中仓库加速镜像可以去阿里云的:容器镜像服务-> 镜像加速器中选择加速地址替换下面的加速地址也就是registry-mirrors;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-'EOF' { "registry-mirrors": ["https://82m9ar63.mirror.aliyuncs.com"], "exec-opts": ["native.cgroupdriver=systemd"], "log-driver": "json-file", "log-opts": { "max-size": "100m" }, "storage-driver": "overlay2" } EOF sudo systemctl daemon-reload sudo systemctl restart docker
6.镜像和容器的区别
Docker命令实战
常用命令
基础实战
1、找镜像
去docker hub ,找到nginx镜像
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 docker pull nginx #下载最新版 镜像名:版本名(标签) docker pull nginx:1.20.1 docker pull redis #下载最新 docker pull redis:6.2.4 ## 下载来的镜像都在本地 docker images #查看所有镜像 ##删除镜像 docker rmi redis ##删除中的redis为 redis = redis:latest docker rmi 镜像名:版本号/镜像id ##每个镜像都有一个id,可以根据id删除镜像 ##如果要删除的镜像真正使用,
Nginx(发音为 “engine-x”)是一个用于 HTTP、HTTPS、SMTP、POP3 和 IMAP 协议的开源反向代理服务器,以及负载均衡器、HTTP 缓存和 Web 服务器(原始服务器)。 nginx 项目一开始就非常注重高并发、高性能和低内存占用。它是根据 2 条款 BSD 类许可证获得许可的,并且可以在 Linux、BSD 变体、Mac OS X、Solaris、AIX、HP-UX 以及其他 *nix 版本上运行。它还具有适用于 Microsoft Windows 的概念验证端口。
docker删除:
docker rm 和docker rmi的区别
docker rm 用于删除容器,而 docker rmi 用于删除镜像。
docker rm 可以删除正在运行的容器,而 docker rmi 只能删除未被使用的镜像。
删除容器可以使用容器 ID 或容器名称,而删除镜像可以使用镜像 ID 或镜像标签。
如何批量删除运行中的容器
docker rm -f $(docker ps -q)这里的docker ps -q是列出正在运行的容器,$()是将括号里面执行结果当做参数传递给docker rm 命令。
要删除多个镜像,可以将它们列在一个命令中
docker rmi image1:tag image2:tag image3:tag
要删除多个容器,你可以使用以下命令:
docker rm container1 container2 container3 …
2、启动容器
启动nginx应用容器,并映射88端口,测试的访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 docker run [OPTIONS] IMAGE [COMMAND] [ARG...] 【docker run 设置项 镜像名 】 镜像启动运行的命令(镜像里面默认有的 ,一般不会写) # -d:后台运行 # --restart=always: 开机自启 docker run --name=mynginx -d --restart=always -p 88:80 nginx ##--name:给启动的镜像生成的容器起一个名字(启动后通过docker ps可以查看启动的容器 其中有个names属性,这里的--name就是设置names属性的, ##-d:是后台启动也就是不在当前的终端启动,而是在后台启动。如果不选择这个 就会在当前的终端启动,且占据着当前的终端,无法在当前的终端使用其他命令。 ##88:80就是端口映射,其中的88是主机的端口,80是容器的端口。 # 查看正在运行的容器 docker ps # 查看所有,包括停止运行的容器 docker ps -a # 删除停止的容器(要指定容器的id/名字) docker rm 容器id/名字 docker rm -f mynginx #强制删除正在运行中的 #停止容器 docker stop 容器id/名字 #再次启动 docker start 容器id/名字 #应用开机自启 docker update 容器id/名字 --restart=always
端口映射:
每个容器相当于一个小的Linux,每个容器都可以设置自己的端口,但是主机也有个端口88.端口映射就是后面我们通过公网ip访问88端口的时候实际上是访问nginx容器的80端口。其中容器启动的时候设置了端口映射可以通过docker ps命名查看到的
这里就可以通过公网ip,端口88(注意不要添加http)访问80的nginx.
3、修改容器内容(可以去dockpub官网查看相关容器的用法)
修改默认的index.html 页面
1、进容器内部修改
1 2 3 4 # 进入容器内部的系统,修改容器内容 docker exec -it 容器id /bin/bash ##容器相当于一个小的linux这里是进入小linux中的shell当然这里也可以选择 /bin/sh,
再次登录的结果:
使用exit就退出容器。
2、挂载数据到外部修改
1 2 3 4 5 6 docker run --name=mynginx \ -d --restart=always \ -p 88:80 -v /data/html:/usr/share/nginx/html:ro \ nginx # 修改页面只需要去 主机的 /data/html
4、提交改变
将自己修改好的镜像提交
1 2 3 4 5 6 7 8 9 10 11 12 13 docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]] docker commit -a "leifengyang" -m "首页变化" 341d81f7504f guignginx:v1.0 ##这个命令有以下几个部分: -a "leifengyang":指定作者信息为 "leifengyang",表示创建镜像的作者。 -m "首页变化":指定提交消息为 "首页变化",用于描述这个镜像的变化 或修改。 341d81f7504f:容器的 ID,表示要基于此容器创建新的镜像。 guignginx:v1.0:新镜像的名称和标签,这里将创建一个名为 "guignginx" ,标签为 "v1.0" 的镜像。 执行该命令后,Docker 将根据容器的当前状态创建一个新的镜像,并将其命 名为 "guignginx:v1.0",包含了您在容器中所做的更改。
提交改变后,如果后面删除了原来的镜像,想要运行新的镜像的时候,如果用
docker run --name=mynginx -d --restart=always -p 88:80 nginx命令
运行镜像时,运行的是默认镜像。这个时候可以运行刚刚提交的镜像
docker run --name=mynginx -d --restart=always -p 88:80 guignginx:v1.0
这样就可以运行和刚刚删除的容器一样的容器而不必重新在新的容器上修改。
镜像保存:
1、镜像传输
1 2 3 4 5 6 7 8 9 10 11 # 将镜像保存成压缩包 docker save -o abc.tar guignginx:v1.0 # 别的机器加载这个镜像 docker load -i abc.tar #-i 表示输入文件(input file),指定要加载的镜像存储文件的路径。 abc.tar 是一个镜像存储文件,通过该命令,Docker 将读取该文件并加载其 中的镜像到本地 Docker 引擎中。 # 离线安装
5、推送远程仓库
推送镜像到docker hub;应用市场(前提要注册,然后自己创建一个仓库)
1 2 docker tag local-image:tagname new-repo:tagname docker push new-repo:tagname
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 # 把旧镜像的名字,改成仓库要求的新版名字 docker tag guignginx:v1.0 leifengyang/guignginx:v1.0 # 通过执行该命令,Docker 将为镜像 guignginx:v1.0 创建一个新的标签 leifengyang/guignginx:v1.0。这个操作不会创建新的镜像副本, 只是为现有镜像添加了一个新的标签。这样,您可以通过两个不同的标签来 引用同一个镜像。 # 登录到docker hub,只需要登录一次,下次启动服务器的时(没有退出)不需要登录直接 push docker login docker logout(推送完成镜像后退出) # 推送 docker push leifengyang/guignginx:v1.0 # 别的机器下载 docker pull leifengyang/guignginx:v1.0
6、补充
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 docker logs 容器名/id 排错 docker exec -it 容器id /bin/bash # docker 经常修改nginx配置文件 docker run -d -p 80:80 \ -v /data/html:/usr/share/nginx/html:ro \ -v /data/conf/nginx.conf:/etc/nginx/nginx.conf \ --name mynginx-02 \ nginx #-v /data/html:/usr/share/nginx/html:ro:将主机的 /data/html 目录挂载到容器内的 /usr/share/nginx/html 目录,并设置为只读模式 (ro)。这样,容器内的 Nginx 将使用主机上的 HTML 文件。 -v /data/conf/nginx.conf:/etc/nginx/nginx.conf: 将主机的 /data/conf/nginx.conf 文件挂载到容器内的 /etc/nginx/nginx.conf 文件,用于替换容器内的 Nginx 配置文件。 --name mynginx-02:指定容器的名称为 mynginx-02。 nginx:要运行的镜像名称。 #把容器指定位置的东西复制出来 docker cp 5eff66eec7e1:/etc/nginx/nginx.conf /data/conf/nginx.conf #把外面的内容复制到容器里面 docker cp /data/conf/nginx.conf 5eff66eec7e1:/etc/nginx/nginx.conf
进阶实战
1、编写自己的应用
编写一个HelloWorld应用
https://start.spring.io/
示例代码: https://gitee.com/leifengyang/java-demo.git
2、将应用打包成镜像
编写Dockerfile将自己的应用打包镜像
1、以前
Java为例
SpringBoot打包成可执行jar
把jar包上传给服务
服务器运行java -jar
2、现在
所有机器都安装Docker,任何应用都是镜像,所有机器都可以运行
3、怎么打包-Dockerfile
1 2 3 4 5 6 FROM openjdk:8-jdk-slim LABEL maintainer=leifengyang COPY target/*.jar /app.jar ENTRYPOINT ["java","-jar","/app.jar"]
1 docker build -t java-demo:v1.0 .
思考:
每个应用每次打包,都需要本地编译、再上传服务器、再进行docker构建,如果有1000个应用要打包镜像怎么办?有没有更好的方式?
3、启动容器
启动应用容器
1 docker run -d -p 8080:8080 --name myjava-app java-demo:v1.0
分享镜像
1 2 3 4 5 6 7 8 9 10 11 12 13 14 # 登录docker hub docker login #给旧镜像起名 docker tag java-demo:v1.0 leifengyang/java-demo:v1.0 # 推送到docker hub docker push leifengyang/java-demo:v1.0 # 别的机器 docker pull leifengyang/java-demo:v1.0 # 别的机器运行 docker run -d -p 8080:8080 --name myjava-app java-demo:v1.0
4、部署中间件
部署一个Redis+应用,尝试应用操作Redis产生数据
1 2 3 4 5 6 7 8 9 10 11 docker run [OPTIONS] IMAGE [COMMAND] [ARG...] #redis使用自定义配置文件启动 docker run -v /data/redis/redis.conf:/etc/redis/redis.conf \ -v /data/redis/data:/data \ -d --name myredis \ -p 6379:6379 \ redis:latest redis-server /etc/redis/redis.conf
Redis配置密码:
连接Redis:
测试使用redis:
1、NoSQL
Redis整合
**Redis不会的同学:参照 阳哥-《Redis7》 **https://www.bilibili.com/video/BV13R4y1v7sP?p=1
**HashMap: key:value
**
1. 场景整合
依赖导入
1 2 3 4 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
配置
1 2 spring.data.redis.host=192.168.200.100 spring.data.redis.password=Lfy123!@!
测试
1 2 3 4 5 6 7 8 @Autowired StringRedisTemplate redisTemplate; @Test void redisTest(){ redisTemplate.opsForValue().set("a","1234"); Assertions.assertEquals("1234",redisTemplate.opsForValue().get("a")); }
2. 自动配置原理
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports中导入了RedisAutoConfiguration
、RedisReactiveAutoConfiguration和RedisRepositoriesAutoConfiguration。所有属性绑定在RedisProperties
中
RedisReactiveAutoConfiguration属于响应式编程,不用管。RedisRepositoriesAutoConfiguration属于 JPA 操作,也不用管
RedisAutoConfiguration 配置了以下组件
LettuceConnectionConfiguration: 给容器中注入了连接工厂LettuceConnectionFactory,和操作 redis 的客户端DefaultClientResources。
RedisTemplate<Object, Object>
: 可给 redis 中存储任意对象,会使用 jdk 默认序列化方式。
StringRedisTemplate
: 给 redis 中存储字符串,如果要存对象,需要开发人员自己进行序列化。key-value都是字符串进行操作··,如果key和value中有一个是实体对象可以将对象转化为json数据(字符串)来进行操作、,
3. 定制化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Configuration public class AppRedisConfiguration { /** * 允许Object类型的key-value,都可以被转为json进行存储。 * @param redisConnectionFactory 自动配置好了连接工厂 * @return */ @Bean public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<Object, Object> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); //把对象转为json字符串的序列化工具 template.setDefaultSerializer(new GenericJackson2JsonRedisSerializer()); return template; } }
2. redis客户端
RedisTemplate、StringRedisTemplate: 操作redis的工具类
要从redis的连接工厂获取链接才能操作redis
Redis客户端
Lettuce: 默认
Jedis:可以使用以下切换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <exclusions> <exclusion> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> </exclusion> </exclusions> </dependency> <!-- 切换 jedis 作为操作redis的底层客户端--> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency>
3. 配置参考
1 2 3 4 5 6 7 8 9 10 11 spring.data.redis.host=8.130.74.183 spring.data.redis.port=6379 #spring.data.redis.client-type=lettuce #设置lettuce的底层参数 #spring.data.redis.lettuce.pool.enabled=true #spring.data.redis.lettuce.pool.max-active=8 spring.data.redis.client-type=jedis spring.data.redis.jedis.pool.enabled=true spring.data.redis.jedis.pool.max-active=8
总结:
1.
消息服务:
1.docker安装kafka(没学完)
Docker安装Kafka教程(超详细)_乾坤鸟的博客-CSDN博客
docker network create app-tier --driver bridge
1 2 3 4 5 6 7 8 9 10 使用命令 docker network create app-tier --driver bridge 可以创建一个 名为 "app-tier" 的 Docker 网络,并使用桥接(bridge)驱动程序。 这个命令会创建一个桥接网络,桥接网络是 Docker 中默认的网络驱动程序。 桥接网络用于连接运行在同一 Docker 主机上的多个容器,使它们能够使用容 器名称作为主机名进行通信。 通过创建 "app-tier" 网络,您可以启动容器并将它们连接到该网络,以便 实现容器之间的通信。同一网络内的容器可以使用它们的容器名称作为主机名 来相互访问。
1 2 3 4 5 6 7 8 docker run -d --name kafka-server \ --network app-tier \ -p 9092:9092 \ -e ALLOW_PLAINTEXT_LISTENER=yes \ -e KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper-server:2181 \ -e KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://8.130.50.249:9092\ bitnami/kafka:latest
2.安装kafkaUi
【docker】docker安装带ui界面的kafka_docker ui 安装_努力的gopher的博客-CSDN博客
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 version: "2" services: zookeeper: image: docker.io/bitnami/zookeeper:3.8 ports: - "22181:2181" volumes: - "zookeeper_data:/bitnami" environment: - ALLOW_ANONYMOUS_LOGIN=yes kafka: container_name: kafka1 image: docker.io/bitnami/kafka:3.4 ports: - "9192:9092" volumes: - "kafka_data:/bitnami" environment: - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181 - ALLOW_PLAINTEXT_LISTENER=yes - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://8.130.50.249:9192 - KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE=true depends_on: - zookeeper kafka-ui: image: provectuslabs/kafka-ui:latest container_name: kafka-ui restart: always ports: - 10010:8080 volumes: - /etc/localtime:/etc/localtime environment: - KAFKA_CLUSTERS_0_NAME=local - KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS=kafka1:9092 volumes: zookeeper_data: driver: local kafka_data: driver: local