springboot2
SpringBoot 2
2022.12.24 - 12.31
Spring Boot是由Pivotal团队提供的全新框架 ,其设计目的是用来简化 新Spring 应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,Spring Boot致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者。
SpringBoot 介绍
为什么使用 SpringBoot
Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”.
能快速创建出生产级别的Spring应用
SpringBoot 优点
Create stand-alone Spring applications
- 创建独立Spring应用
Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files)
- 内嵌web服务器
Provide opinionated ‘starter’ dependencies to simplify your build configuration
- 自动starter依赖,简化构建配置
Automatically configure Spring and 3rd party libraries whenever possible
- 自动配置Spring以及第三方功能
Provide production-ready features such as metrics, health checks, and externalized configuration
- 提供生产级别的监控、健康检查及外部化配置
Absolutely no code generation and no requirement for XML configuration
- 无代码生成、无需编写XML
SpringBoot 是整合Spring 技术栈的一站式框架
SpringBoot 是简化 Spring 技术栈的快速开发的脚手架
SpringBoot 的缺点
- 版本迭代快,需要时刻关注变化
- 封装太深,内部原理复杂,不容易精通
时代背景
微服务
James Lewis and Martin Fowler (2014) 提出微服务完整概念。https://martinfowler.com/microservices/
In short, the microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies.– James Lewis and Martin Fowler (2014)
- 微服务是一种架构风格
- 一个应用拆分为一组小型服务
- 每个服务运行在自己的进程内,也就是可独立部署和升级
- 服务之间使用轻量级HTTP交互
- 服务围绕业务功能拆分
- 可以由全自动部署机制独立部署
- 去中心化,服务自治。服务可以使用不同的语言、不同的存储技术
分布式
分布式的困难
- 远程调用
- 服务发现
- 负载均衡
- 服务容错
- 配置管理
- 服务监控
- 链路追踪
- 日志管理
- 任务调度
- ……
分布式的解决
- SpringBoot + SpringCloud
云原生
原生应用如何上云。 Cloud Native
上云的困难
- 服务自愈
- 弹性伸缩
- 服务隔离
- 自动化部署
- 灰度发布
- 流量治理
- ……
上云的解决
如何学习 SpringBoot
官方文档架构
查看版本新特性;
https://github.com/spring-projects/spring-boot/wiki#release-notes
SpringBoot 快速启动
通过一个 小案例快速启动 SpringBoot
SpringBoot 入门
maven 设置
1 | <mirrors> |
Hello,SpringBoot
需求 : 向浏览器发送 /hello 请求,响应 hello,SpringBoot2
创建 maven 工程
引入 依赖
1 | <parent> |
创建主程序
1 | /** |
编写业务
1 |
|
测试
直接运行 main 方法
简化配置
application.properties
1 | server.port=8088 |
简化部署
1 | <build> |
自动装配原理
依赖管理
父项目做依赖管理
依赖管理
1 | <parent> |
父项目
1 | <parent> |
几乎声明了所有开发中常用的依赖的版本号,自动版本仲裁机制
开发导入 starter 场景启动器
有很多 spring-boot-starter-* : *所指的就是某种场景
只要引入 starter,这个场景的所有常规需要的依赖我们都自动导入
SpringBoot 所有支持的场景 :
*-spring-boot-starter : 第三方为我们提供的简化开发的场景启动器
所有场景启动器最底层的依赖
- ```xml
org.springframework.boot spring-boot-starter 2.3.4.RELEASE compile 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<br>
##### 版本号管理
- 无需关注版本号,自动版本仲裁
- 引入依赖都可以不写版本号
- 引入非版本仲裁的 jar,要写版本号
- 可以修改默认的版本号
- 查看 spring-boot-dependencies 里面规定当前依赖的版本用的 key
- 在 当前项目里面重写配置
- ```xml
<properties>
<mysql.version>5.1.43</mysql.version>
</properties>
- ```xml
自动配置
自动配好 Tomcat
引入 Tomcat 依赖
配置 Tomcat
- ```xml
org.springframework.boot spring-boot-starter-tomcat 2.3.4.RELEASE compile 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
- 自动配置 SpringMVC
- 引入 SpringMVC 全套组件
- 自动配好 SpringMVC 常用组件(功能)
- 自动配置 Web 常见功能 ,如 字符编码问题:
- SpringBoot 帮我们配置好了所有 Web 开发的常见场景
- 默认的包结构
- 主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来
- 无需以前的包扫描配置
- 想要改变扫描路径, `@SpringBootApplication(scanBasePackage="com.cs7eric")`
- 或者 `@ComponentScan ` 指定扫描路径
- ```java
@SpringBootApplication
等同于
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.cs7eric.springboot")
- ```xml
各种配置拥有默认值
- 默认配置最终都是映射到某个类上,如 : MultipartProperties
- 配置文件的值最终会绑定某个类,这个类会在容器中创建对象
按需加载所有自动配置项
- 非常多的 starter
- 引入了 那些场景,这个场景的自动配置才会开启
- SpringBoot 所有的自动配置功能都在 spring-boot-autoconfigure 包里面
容器功能
组件添加
@Configuration
- 基本使用
- Full 模式和 Lite 模式
- 示例
- 最佳实战
- 配置 类组件之间无依赖关系 用 Lite 模式 加速容器启动过程,减少判断
- 类上有@Component注解
- 类上有@ComponentScan注解
- 类上有@Import注解
- 类上有@ImportResource注解
- 类上没有任何注解,但是类中存在@Bean方法
- 类上有@Configuration(proxyBeanMethods = false)注解
- 运行时不用生成CGLIB子类,提高运行性能,降低启动时间,可以作为普通类使用。但是不能声明@Bean之间的依赖
- 配置类组件之间有依赖关系,方法会被调用之前单实例组件,用 Full 模式
- 标注有@Configuration或者@Configuration(proxyBeanMethods = true)的类被称为Full模式的配置类。
- 单例模式能有效避免Lite模式下的错误。性能没有Lite模式好
- 配置 类组件之间无依赖关系 用 Lite 模式 加速容器启动过程,减少判断
@Configuration 使用示例
- 配置类里面使用 @Bean 标注在方法上给容器注册组件,默认也是单实例的
- 配置类本身也是 组件
- proxyBeanMethods : 代理 bean 的方法
- Full (
proxyBeanMethods = "true"
) 保证每个 @Bean 方法被调用多少次返回的组件都是单实例的 - Lite (
proxyBeanMethods= "false"
)每个 @Bean 方法被调用多少次返回的组件都是新创建的
- Full (
组件依赖必须使用 Full 模式默认。其他默认是 Lite 模式
1 | /** |
1 | /** |
@Bean、@Component、@Controller、@Service、@Repository
@ComponentScan、@Import
@Import
@Import({User.class,DBHelper.class})
给容器中自动创建出这两个类型的组件、默认组件的名字就是全类名
1 |
|
@Import 高级用法 :https://www.bilibili.com/video/BV1gW411W7wy?p=8
Conditional
条件装配 : 满足 Conditional 指定的条件,则进行 组件注入
1 |
|
原生配置文件引入
@ImportResource
1 |
|
配置绑定
如何使用 Java 读取到 properties 文件中的内容,并且将它 封装到 JavaBean中,以供随时使用
1 | public class getProperties { |
@ConfigurationProperties
1 | /** |
@EnableConfigurationProperties + @ConfigurationProperties
@Component + @ConfigurationProperties
1 |
|
自动配置原理入门
引导加载自动配置类
1 |
|
@SpringBootConfiguration
代表当前是一个配置类
@ComponentScan
指定扫描那些 Spring 注解
@EnableAutoConfiguration
1 |
|
@AutoConfigurationPackage
自动配置包 指定了默认的包规则
1 | //给容器中导入一个组件 |
@Import(AutoConfigurationSelector.class)
- 利用
getAutoConfigurationEntry(annotationMetadata)
给容器批量导入一些组件 - 调用
List<String> configurations = getCandidateConfigurations(annotationMetadata,attributes)
获取到所有需要导入到容器中的配置类 - 利用 工厂加载
Map<String,List<String>> loadSpringFactories(@Nullable ClassLoader classLoader)
得到所有的组件 - 从 META-INF/spring.factories 位置来加载一个文件
- 默认扫描我们当前系统里面所有 META-INF/spring.factries 位置的文件
- spring-boot-autoconfigure-2.3.4.RELEASE.jar 包里面也有 META-INF/spring.factories
文件里面写死了 spring-boot 一启动就要给容器中加载的所有配置类
1 | 文件里面写死了spring-boot一启动就要给容器中加载的所有配置类 |
按需开启自动配置项
虽然我们 127 个场景的所有自动配置启动的时候默认全部加载 xxxxAutoConfiguration
按照条件装配原则 (**@Conditional**),最终会按需装配
修改默认配置
Spring-boot 默认会在 底层配置好所有的组件,但是如果用户自己配置了,以用户的优先
总结:
- Spring-boot 先加载所有的自动配置类 xxxAutoConfiguration
- 每个 自动配置类按照条件进行生效,默认都会绑定配置文件指定的值,xxxProperties 里面拿。xxxProperties 和 配置文件进行了绑定
- 生效的配置类就会给容器装配很多组件
- 只要容器中有这些 组件,相当于很多组件都有了
- 定制化配置
- 用户直接自己 @Bean 替换底层的组件
- 用户去看这个组件是获取的配置文件什么值 就去修改
xxxxxAutoConfiguration —> 组件 —> xxxxProperties里面拿值 —-> application.properties
最佳实践
- 引入 场景依赖
- 查看自动配置了那些(选做)
- 自己分析,引入场景对应的自动配置一般都生效了
- 配置文件中 debug=true 开启自动配置报告 Negative(不生效)| Positive (生效)
- 是否需要修改
- 参考文档修改配置项
- 自定义加入或者替换组件
- @Bean
- @Component
- … …
- 自定义器 xxxCustomer
开发小技巧
Lombok
简化 JavaBean 开发
1 | <dependency> |
dev-tools
1 | <dependency> |
项目或者页面修改以后 : Ctrl + F9
Spring Initailizr(项目初始化向导)
SpringBoot 核心技术
配置文件
文件类型
properties
同以前 properties的 用法
yaml
简介
YAML 是 “YAML Ain’t Markup Language” (YAML 不是一种标记语言)的递归缩写。在开发这种语言时,YAML 的意思其实是 “Yet Another Markup Language”, (仍是一种标记语言)
非常适合用来做以数据为中心的配置文件
基本语法
- key: value; k v 之间有空格
- 大小写敏感
- 使用缩进表示层级关系
- 缩进不允许使用 tab,只允许 空格
- 缩进的空格数不重要,只要相同层级的元素左对齐即可
- ‘#’ 表示注释
- 字符串无需加引号,如果要加,’ ‘ 和 “ “ 表示字符串内容会被 转义 / 不转义
数据类型
字面量:单个的、不可再分的值。 date、boolean、string、number、null
k: v
对象:键值对的集合。 map、hash、set、object
- ```yaml
行内写法: k: {k1:v1,k2:v2,k3:v3}
#或
k:
k1: v1
k2: v2
k3: v31
2
3
4
5
6
7
8
9
10
- 数组:一组按次序排列的值。array、list、queue
- ```yaml
行内写法: k: [v1,v2,v3]
#或者
k:
- v1
- v2
- v3
- ```yaml
示例
```java
@Data
public class Person {private String userName; private Boolean boss; private Date birth; private Integer age; private Pet pet; private String[] interests; private List<String> animal; private Map<String, Object> score; private Set<Double> salarys; private Map<String, List<Pet>> allPets;
}
@Data
public class Pet {
private String name;
private Double weight;
}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
- ```yaml
# yaml表示以上对象
person:
userName: zhangsan
boss: false
birth: 2019/12/12 20:12:33
age: 18
pet:
name: tomcat
weight: 23.4
interests: [篮球,游泳]
animal:
- jerry
- mario
score:
english:
first: 30
second: 40
third: 50
math: [131,140,148]
chinese: {first: 128,second: 136}
salarys: [3999,4999.98,5999.99]
allPets:
sick:
- {name: tom}
- {name: jerry,weight: 47}
health: [{name: mario,weight: 47}]
配置提示
自定义的类和配置文件绑定一般没有提示
1 | <dependency> |
Web 开发
SpringMVC 自动配置概览
SpringBoot provides auto-autoconfiguration for Spring MVC that works well with most applications. (大多场景我们都无需自定义配置)
The auto-configuration adds the following features on top of Spring’s defaults:
Inclusion of
ContentNegotiatingViewResolver
andBeanNameViewResolver
beans.- 内容协商视图解析器和BeanName视图解析器
Support for serving static resources, including support for WebJars (covered later in this document )).
- 静态资源(包括webjars)
Automatic registration of
Converter
,GenericConverter
, andFormatter
beans.- 自动注册
Converter,GenericConverter,Formatter
- 自动注册
Support for
HttpMessageConverters
(covered later in this document ).- 支持
HttpMessageConverters
(后来我们配合内容协商理解原理)
- 支持
Automatic registration of
MessageCodesResolver
(covered later in this document ).- 自动注册
MessageCodesResolver
(国际化用)
- 自动注册
Static
index.html
support.- 静态index.html 页支持
Custom
Favicon
support (covered later in this document ).- 自定义
Favicon
- 自定义
Automatic use of a
ConfigurableWebBindingInitializer
bean (covered later in this document ).- 自动使用
ConfigurableWebBindingInitializer
,(DataBinder负责将请求数据绑定到JavaBean上)
- 自动使用
If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own
@Configuration
class of typeWebMvcConfigurer
but without@EnableWebMvc
.不用@EnableWebMvc注解。使用
@Configuration
+WebMvcConfigurer
自定义规则
If you want to provide custom instances of
RequestMappingHandlerMapping
,RequestMappingHandlerAdapter
, orExceptionHandlerExceptionResolver
, and still keep the Spring Boot MVC customizations, you can declare a bean of typeWebMvcRegistrations
and use it to provide custom instances of those components.声明
**WebMvcRegistrations**
改变默认底层组件
If you want to take complete control of Spring MVC, you can add your own
@Configuration
annotated with@EnableWebMvc
, or alternatively add your own@Configuration
-annotatedDelegatingWebMvcConfiguration
as described in the Javadoc of@EnableWebMvc
.使用
**@EnableWebMvc+@Configuration+DelegatingWebMvcConfiguration 全面接管SpringMVC**
简单功能分析
静态资源访问
静态资源目录
只要静态资源放在类路径下 : called /static
(or /public
or /resources
or /META-INF/resources
访问 : 当前项目根路径/ + 静态资源名
原理: 静态映射 /**
请求进来,先去找 Controller
能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则 响应 404
改变默认的静态资源路径
1 | spring: |
静态资源访问前缀
默认无前缀
1 | spring: |
当前项目 + static-path-pattern + 静态资源名 = 静态资源文件下找
webjar
自动映射 /webjars/**
1 | <dependency> |
访问地址:http://localhost:8080/webjars/jquery/3.5.1/jquery.js 后面地址要按照依赖里面的包路径
欢迎页支持
- 静态资源路径下 index.html
- 可以配置 静态资源路径
- 但是不可以配置静态资源的访问前缀。否则导致 index.html 不能被 默认访问
- 可以配置 静态资源路径
1 | spring: |
自定义 Favicon
favicon.ico 放在静态资源目录下即可
1 | spring: |
静态资源配置原理
SpringBoot 启动默认加载 xxxAutoConfiguration 类,自动配置类
SpringMVC 功能的自动配置类 WebMVCAutoConfiguration 生效
- ```java
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {}1
2
3
4
5
6
7
8
9
- 给容器中配置了什么
- ```java
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {}
- ```java
配置文件的相关属性和 xxx 进行了绑定。
- WebMvcProperties == spring.mvc
- ResourceProperties == spring.resources
配置类只有一个有参构造器
- 有参构造器的所有参数的值都会从容器中确定
- ResourceProperties resourceProperties;获取和spring.resources绑定的所有的值的对象
- WebMvcProperties mvcProperties 获取和spring.mvc绑定的所有的值的对象
- ListableBeanFactory beanFactory Spring的beanFactory
- HttpMessageConverters 找到所有的HttpMessageConverters
- ResourceHandlerRegistrationCustomizer 找到 资源处理器的自定义器。
- ServletRegistrationBean 给应用注册Servlet、Filter….
- DispatcherServletPath
1 | public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties, |
资源处理的默认规则
1 |
|
1 | spring: |
1 |
|
欢迎页的处理规则
HandlerMapping : 处理器映射。保存了 每一个 Handler 能处理哪些请求
1 |
|
请求参数处理
请求映射
rest 使用与原理
- @xxxMapping;
- Rest 风格支持 (使用 HTTP 请求方式动词来表示对资源的操作)
- 以前:*/getUser* 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户
- 现在 : /user GET- 获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
- 核心Filter : **HiddenHttpMethodFilter **
- 用法 : 表单 method=post,隐藏域 _method=put
- SpringBoot 中手动开启
- 扩展: 如何把 _method 这个名字自定义
1 |
|
Rest 原理(表单提交要使用 REST 的时候)
- 表单提交会带上
_method=PUT
- 请求过来被
HiddenHttpMethodFilter
拦截- 请求是否 正常,并且是 POST
- 获取到
_method
的值 - 兼容以下请求
- PUT
- DELETE
- PATCH
- 原生 request (post),包装模式 requestWrapper 重写了
getMethod
方法 , 返回的是 传入的值 - 过滤器链放行的时候用 wrapper,以后的方法调用
getMethod
是调用requestWrapper
的
- 获取到
- 请求是否 正常,并且是 POST
Rest 使用客户端工具
- 如 POSTMAN 之间发送 PUT、DELETE 等方式,无需 Filter
1 | spring: |
请求映射原理
SpringMVC 功能分析 都从 org.springframework.web.servlet.DispatcherServlet -> doDispatch()
1 | protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { |
mappedHandler = getHandler(processedRequest);
找到当前请求使用哪个 Handler(Controller 的方法)处理HandlerMapping : 处理器映射 xxx -> xxxx
RequestMappingHandlerMapping : 保存了所有 @RequestMapping 和 Handler 的映射规则
所有的 请求映射 都在 HandlerMapping 中
- SpringBoot 自动配置欢迎页的 WelcomePageHandlerMapping。访问/能访问到 index.html
- springboot 自动配置了 默认的 RequestMappingHandlerMapping
- 请求进来,挨个尝试所有的 HandlerMapping 看是否有请求信息
- 如果有 就找到这个请求对应的 Handler
- 如果没有就是下一个 HandlerMapping
- 我们需要一些自定义的映射处理,我们也可以自己给容器中 放 HandlerMapping。自定义 HandlerMapping
1 | protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { |
普通参数域基本注解
注解
- @PathVariable
- @RequestHeader
- @ModelAttribute
- @RequestParam
- @MatrixVariable
- @CookieValue
- @RequestBody
1 |
|
@MatrixVariable
- 语法: 请求路径:/cars/sell;low=34;brand=byd,audi,yd
- SpringBoot默认是禁用了矩阵变量的功能
- 手动开启:原理。对于路径的处理。UrlPathHelper进行解析。
- removeSemicolonContent(移除分号内容)支持矩阵变量的
- 矩阵变量必须有url路径变量才能被解析
1 |
|
@MatrixVariable(value = "age",pathVar = "boosId") Integer bossAge
ServletAPI
WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId
ServletRequestMethodArgumentResolver 以上部分参数
1 |
|
复杂参数
Map、Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes( 重定向携带数据)、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder
1 | Map<String,Object> map, Model model, HttpServletRequest request 都是可以给request域中放数据, |
Map、Model 类型的参数,会返回 **mavContainer.getModel(); ->
**BindingAwareModelMap : 是 Model 也是 Map
mavContainer.getModel(); 获取到值的
自定义对象参数
可以自动类型转换与格式化,可以级联封装
1 | /** |
POJO 封装过程
ServletModelAttributeMethodProcessor
参数处理原理
- HandlerMapping 中找到能处理请求的 Handler(Controller.method();)
- 为 当前 Handler 找一个适配器 HandlerAdaptor; RequestMappingHandlerAdaptor
- 适配器执行目标方法并确定参数的每一个值
HandlerAdaptor
- 0 - 支持方法上标注 @RequestMapping
- 1 - 支持函数式编程的
- xxxx
执行 目标方法
1 | // Actually invoke the handler. |
1 | mav = invokeHandlerMethod(request, response, handlerMethod); //执行目标方法 |
参数解析器 - HandlerMethodArgumentResolver
确定将要执行的目标方法的每一个参数的值是什么
SpringMVC 目标方法能写多少种 参数类型。取决于参数解析器
- 当前解析器是否支持解析这种参数
- 支持就调用 resolverArgument
返回值处理器
如何确定目标方法每一个参数的值
1 | ============InvocableHandlerMethod========================== |
挨个判断所有参数解析器那个支持解析这个参数
- ```java
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
if (resolver.supportsParameter(parameter)) {
result = resolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}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
- 解析这个参数的值
- 调用各自 **HandlerMethodArgumentResolver** 的 **resolveArgument** 方法即可
- 自定义类型参数 封装 POJO
**ServletModelAttributeMethodProcessor** 这个参数处理器支持
- 判断是否是普通类型
- ```java
public static boolean isSimpleValueType(Class<?> type) {
return (Void.class != type && void.class != type &&
(ClassUtils.isPrimitiveOrWrapper(type) ||
Enum.class.isAssignableFrom(type) ||
CharSequence.class.isAssignableFrom(type) ||
Number.class.isAssignableFrom(type) ||
Date.class.isAssignableFrom(type) ||
Temporal.class.isAssignableFrom(type) ||
URI.class == type ||
URL.class == type ||
Locale.class == type ||
Class.class == type));
}- ```java
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
Assert.state(mavContainer != null, “ModelAttributeMethodProcessor requires ModelAndViewContainer”);
Assert.state(binderFactory != null, “ModelAttributeMethodProcessor requires WebDataBinderFactory”);
String name = ModelFactory.getNameForParameter(parameter);
ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
if (ann != null) {
mavContainer.setBinding(name, ann.binding());
}
Object attribute = null;
BindingResult bindingResult = null;
if (mavContainer.containsAttribute(name)) {
attribute = mavContainer.getModel().get(name);
}
else {
// Create attribute instance
try {
attribute = createAttribute(name, parameter, binderFactory, webRequest);
}
catch (BindException ex) {
if (isBindExceptionRequired(parameter)) {
// No BindingResult parameter -> fail with BindException
throw ex;
}
// Otherwise, expose null/empty value and associated BindingResult
if (parameter.getParameterType() == Optional.class) {
attribute = Optional.empty();
}
bindingResult = ex.getBindingResult();
}
}
if (bindingResult == null) {
// Bean property binding and validation;
// skipped in case of binding failure on construction.
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
if (!mavContainer.isBindingDisabled(name)) {
bindRequestParameters(binder, webRequest);
}
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
// Value type adaptation, also covering java.util.Optional
if (!parameter.getParameterType().isInstance(attribute)) {
attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
}
bindingResult = binder.getBindingResult();
}
// Add resolved attribute and BindingResult at the end of the model
Map<String, Object> bindingResultModel = bindingResult.getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);
return attribute;
}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
- `WebDataBinder binder = binderFactory.createBinder(webRequest,attribute,name)`
- `WebDataBinder` : web 数据绑定器,将请求参数的值绑定到指定的 JavaBean 里面
- WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到 JavaBean中
- `GenericConversionService` : 在设置每一个值的时候,找他里面的所有 converter ,那个可以将这个数据类型 (request 带来参数的字符串) 转换到 指定的 类型 (JavaBean -- Integer)
- `@FunctionalInterface public interface Concerter<S,T>`
<img src="https://cs7eric-image.oss-cn-hangzhou.aliyuncs.com/images/image-20221225221356579.png" alt="image-20221225221356579" style="zoom:67%;" />
![image-20221225221420066](https://cs7eric-image.oss-cn-hangzhou.aliyuncs.com/images/image-20221225221420066.png)
<br>
未来我们可以 给 WebDataBinder 里面放自己的 Converter
**`private static final class** StringToNumber<T **extends** Number> **implements** Converter<String, T>`**
<br>
自定义 Converter
```java
//1、WebMvcConfigurer定制化SpringMVC的功能
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
// 不移除;后面的内容。矩阵变量功能就可以生效
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new Converter<String, Pet>() {
@Override
public Pet convert(String source) {
// 啊猫,3
if(!StringUtils.isEmpty(source)){
Pet pet = new Pet();
String[] split = source.split(",");
pet.setName(split[0]);
pet.setAge(Integer.parseInt(split[1]));
return pet;
}
return null;
}
});
}
};
}
- ```java
- ```java
目标方法执行完成
将所有的 数据都放在 ModelAndViewContainer;包含要去的页面地址 和 Model 数据
处理派发结果
processDispatchResult(processRequest,response,mappedHandler,mv,dispatchException);
renderMergedOutputModel(mergeModel,getRequestToExpose(request),response);
1 | InternalResourceView: |
暴露模型作为请求域属性
//Expose the model object as request attributes.
exposeModelAsRequestAttributes(model, request)
1 | protected void exposeModelAsRequestAttributes(Map<String, Object> model, |
数据响应与内容协商
响应 JSON
jackson.jar + @ResponseBody
web 场景自动引入了 JSON 场景
给前端自动返回 JSON 数据
返回值解析器
try { this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest); }
1
2
3
4
5
6
7
8
9
10
11
12
- ```java
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}```java
RequestResponseBodyMethodProcessor
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// Try even with null return value. ResponseBodyAdvice could get involved.
// 使用消息转换器进行写出操作
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}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
- **返回值解析器原理**
<img src="https://cs7eric-image.oss-cn-hangzhou.aliyuncs.com/images/image-20221225222931238.png" alt="image-20221225222931238" style="zoom:50%;" />
- 返回值处理器判断是否支持这种类型返回值
- 返回值处理器调用 `handlerReturnValue ` 进行处理
- `RequestResponseBodyMethodProcessor` 可以处理返回值标注了 `@ResponseBody` 注解的
- 利用 `MessageConverter` 进行处理,将数据写为 JSON
- 内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
- 服务器最终会根据自己自身的能力,决定服务器能生产出什么样内容类型的数据
- SpringMVC 会挨个 遍历所有容器底层的 `HttpMessageConverter` ,看看谁能处理
- 得到 `MappingJackson2HttpMessageConverter` 可以将对象写为 JSON
- 利用 `MappingJackson2HttpMessageConverter` 将对象转换为 JSON 再写出去
<br>
###### SpringMVC 支持哪些 返回值
- ModelAndView
- Model
- View
- ResponseEntity
- ResponseBodyEmitter
- StreamingResponseBody
- HttpEntity
- HttpHeaders
- Callable
- DeferredResult
- ListenableFuture
- CompletionStage
- WebAsyncTask
- 有 @ModelAttribute 且为对象类型的
- @ResponseBody 注解 ---> RequestResponseBodyMethodProcessor;
<br>
###### HttpMessageConverter 原理
- **MessageConverter 规范**
<img src="https://cs7eric-image.oss-cn-hangzhou.aliyuncs.com/images/image-20221225223948298.png" alt="image-20221225223948298" style="zoom:50%;" />
HttpMessageConverter : 看是否支持将此 Class 类型的 对象,转为 MediaType 类型的数据
例子 : Person 对象转为 JSON,或者 JSON 转为 Person
- 默认的 MessageConverter
<img src="https://cs7eric-image.oss-cn-hangzhou.aliyuncs.com/images/image-20221225224151466.png" alt="image-20221225224151466" style="zoom: 33%;" />
- 0 - 只支持 byte 类型的
- 1 - String
- 2 - String
- 3 - Resource
- 4 - ResourceRegion
- 5 - DOMSource.**class** SAXSource.**class** StAXSource.**class** StreamSource.**class** Source.**class**
- 6 - MultiValueMap
- 7 - true
- 8 - true
- 9 - 支持注解方式 xml 处理的
<br>
最终 MappingJackson2HttpMessageConverter 把 对象 转为 JSON (利用底层的 Jackson 的 objectMapper 转换的 )
<img src="https://cs7eric-image.oss-cn-hangzhou.aliyuncs.com/images/image-20221225224606825.png" alt="image-20221225224606825" style="zoom:50%;" />
<br>
##### 内容协商
根据客户端接受能力不同,返回不同类型的 数据
<br>
###### 引入 xml 依赖
```xml
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
postman 分别测试返回 JSON 和 xml
只需要改变请求头中的 Accept 字段。Http 协议中规定的,告诉服务器 本客户端可以接受的数据类型
开启浏览器参数方式内容协商功能
为了方便内容协商,开启基于请求参数的内容协商功能
1 | spring: |
发请求:
确定客户端接收什么样的内容类型
- Parameter 策略优先确定是要返回 JSON 数据 (获取 请求头中的 format的 值 )
- 最终 进行内容协商返回给 客户端 JSON 即可
内容协商原理
判断 当前响应头中是否已经有确定的媒体类型 - MediaType
获取客户端(POSTMAN、浏览器)支持接收的内容类型。(获取客户端 Accept 请求头字段)【application/xml】
contentNegotiationManager
内容协商管理器 : 默认使用 基于请求头的策略HeaderContentNegotiationStrategy
确定客户端可以接收的内容类型
遍历循环所有当前系统的
MessageConverter
,看谁操作这个对象(Person)找到 支持操作 Person 的converter ,把 converter 支持的媒体类型统计出来
客户端需要 【application/xml 】。服务端能力 【10 种、JSON、xml】
进行内容协商的最佳匹配媒体类型
用支持 将对象转为 最佳匹配媒体类型的 converter。调用它进行优化
导入了 Jackson 处理 xml 的 包,xml 的 converter 就会自动进来
1
2
3
4
5
6
7
8
9
10WebMvcConfigurationSupport
jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
if (jackson2XmlPresent) {
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml();
if (this.applicationContext != null) {
builder.applicationContext(this.applicationContext);
}
messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));
}
自定义 MessageConverter
实现多协议数据兼容,JSON、xml、x-cs7eric
@ResponseBody
响应数据出去,调用RequestResponseMethodProcessor
处理- Processor处理方法返回值。通过
MessageConverter
处理 - 所有
MessageConverter
合起来可以支持各种 媒体类型数据的操作(读、写) - 内容协商 找到最终的
messageConverter
SpringMVC 的 什么功能,一个入口给容器添加一个 WebMvcConfigurer
1 |
|
有可能我们添加的自定义的功能会覆盖默认的很多功能,导致一些默认的功能失效
视图解析和模板引擎
视图解析 : SpringBoot 默认不支持 JSP,需要引入第三方模板引擎技术实现页面渲染
视图解析
视图解析原理流程
- 目标方法处理过程中,所有数据都会被放在
ModelAndViewContainer
中,包括 数据和视图地址 - 方法的参数是一个自定义类型对象(从请求参数中确定的),把它重新放在 ModelAndViewContainer
- 任何目标方法执行完成之后都会返回
ModelAndView
(数据和视图地址 ) processorDispatchResult
处理派发结果(页面如何响应)render(mv,request,response)
; 进行页面渲染逻辑- 根据 方法的 String 返回值 得到
View
对象【定义了页面的渲染逻辑】- 所有的视图解析器尝试是否能根据当前返回值得到
View
对象 - 得到了
redirect:/main.html
–> thymeleaf bew RedirectView() ContentNegotiationViewResolver
里面包含了下面所有的 视图解析器,内部还是利用下面所有视图解析器得到 视图对象view.render(mv.getModelInternal(),request,response);
视图对象调用自定义的render
进行页面渲染工作- RedirectView 如何渲染【重定向到一个页面】
- 获取 目标 URL 地址
response.sendRedirect(encodeURL);
- 所有的视图解析器尝试是否能根据当前返回值得到
- 根据 方法的 String 返回值 得到
视图解析
- 返回值以 forward: 开始: new InternalResourceView(forwardUrl); –> 转发request.getRequestDispatcher(path).forward(request, response);
- 返回值以 redirect: 开始: new RedirectView() –》 render就是重定向
- **返回值是普通字符串: new ThymeleafView()—> 自定义视图解析器 + 自定义视图 **
模板引擎 - Thymeleaf
Thymeleaf 简介
Thymeleaf is a modern server-side Java template engine for both web and standalone environments, capable of processing HTML, XML, JavaScript, CSS and even plain text.
现代化、服务端Java模板引擎
基本语法
表达式
表达式名字 | 语法 | 用途 |
---|---|---|
变量取值 | ${…} | 获取请求域、session域、对象等值 |
选择变量 | *{…} | 获取上下文对象值 |
消息 | #{…} | 获取国际化等值 |
链接 | @{…} | 生成链接 |
片段表达式 | ~{…} | jsp:include 作用,引入公共页面片段 |
字面量
文本值: ‘one text’ , ‘Another one!’ ,…数字: 0 , 34 , 3.0 , 12.3 ,…布尔值: true , false
空值: null
变量: one,two,…. 变量不能有空格
文本操作
字符串拼接: +
变量替换: |The name is ${name}|
数学运算
运算符: + , - , * , / , %
布尔运算
运算符: and , or
一元运算: ! , not
比较运算
比较: > , <** **,** **>= , <= ( gt , lt , ge , le **)**等式: == , != ( eq , ne )
条件运算
If-then: (if) ? (then)
If-then-else: (if) ? (then) : (else)
Default: (value) ?: (defaultvalue)
特殊操作
无操作: _
设置属性值 th:attr
设置单个值
1 | <form action="subscribe.html" th:attr="action=@{/subscribe}"> |
设置多个值
1 | <img src="../../images/gtvglogo.png" th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" /> |
以上两个的替代写法 : th:xxx
1 | <input type="submit" value="Subscribe!" th:value="#{subscribe.submit}"/> |
所有的 H5 的 代替写法
https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#setting-value-to-specific-attributes
迭代
1 | <tr th:each="prod : ${prods}"> |
1 | <tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'"> |
条件运算
1 | <a href="comments.html" |
1 | <div th:switch="${user.role}"> |
属性优先级
Thymeleaf 的使用
引入 starter
1 | <dependency> |
自动配置好了 Thymeleaf
1 |
|
自动配置的策略
- 所有的 Thymeleaf 的配置值都在 ThymeleafProperties
- 配置好了 SpringTemplates
- 配好了
ThymeleafViewResolver
- 我们只需要开发页面
1 | public static final String DEFAULT_PREFIX = "classpath:/templates/"; |
页面开发
1 |
|
引入命名空间
xmlns:th="http://www.thymeleaf.org"
搭建后台管理系统
项目创建
Thymeleaf、web-starter、devtools、lombok
静态资源处理
自动配置好,我们只需要把所有静态资源放在 static 文件夹下
路径构建
th:action="@{/login}"
模板抽取
th:insert/replace/include
页面跳转
1 |
|
数据渲染
1 |
|
1 | <table class="display table table-bordered" id="hidden-table-info"> |
拦截器
HandlerInterceptor 接口
1 | /** |
配置拦截器
- 编写一个拦截器 实现
HandlerInterceptor
接口 - 拦截器注册到容器中(实现
WebMvcConfigurer
的addInterceptors
- 指定拦截规则 【如果是拦截所有,静态资源也会被拦截】
1 |
|
拦截器原理
- 根据当前请求,找到
HandlerEsecutionChain
【可以处理请求的 handler 以及 handler 的所有拦截器】 - 先来 顺序执行 所有拦截器的
preHandle
方法- 如果当前 拦截器的
preHandle
返回为 true, 则执行下一个拦截器的preHandle
- 如果当前拦截器 返回为 false,直接倒序执行所有已经执行了 的拦截器的
afterCompletion
- 如果当前 拦截器的
- 如果 任何一个拦截器返回 false,直接跳出不执行 目标方法
- 所有拦截器都返回 true,执行目标方法
- 倒序执行所有拦截器的
postHandle
方法 - 前面的步骤有任何异常都会直接倒序触发
afterCompletion
- 页面成功渲染完成以后,也会倒序触发
afterCompletion
文件上传
页面表单
1 | <form method="post" action="/upload" enctype="multipart/form-data"> |
文件上传代码
1 | /** |
自动配置原理
文件上传自动配置类 - MultipartAutoConfiguration - MultipartProperties
- 自动配置好了
StandardServletMultipartResolver
【文件上传解析器】 - 原理步骤
- 请求进来使用文件上传解析器判断 (
isMultipart
) 并封装 (resolveMultipart
) , 返回MultipartHttpServletRequest
文件上传请求 - 参数解析器来解析请求中的文件内容 封装成为
MultipartFile
- 将 request 中文件信息封装为一个 Map,
MultiValueMap<String,MultipartFile>
FileCopyUtils
实现文件流的拷贝
- 请求进来使用文件上传解析器判断 (
1 |
|
异常处理
错误处理
默认规则
- 默认情况下,SpringBoot 会提供
/error
处理所有错误的映射 - 对应 机器客户端,他将 生成 JSON 响应,其中包含错误,HTTP 状态和异常消息的详细信息。对于浏览器客户端,响应一个 “whitelabel” 错误视图,以 HTML 格式 呈现相同的数据
- 要对其进行自定义,添加
view
解析为error
- 要完全替换默认行为,可以实现
ErrorController
并注册该类型的 Bean 定义,或添加ErrorAttributees
类型的组件,以使用现有机制但替换其内容 error
下的 4xx、5xx 页面会被自动解析
定制错误处理逻辑
自定义错误页
- error/404.html error/5xx.html;有精确的错误状态码页面就匹配精确,没有就找 4xx.html;如果都没有就触发白页
@ControllerAdvice
+@ExceptionHandler
处理全局异常;底层是ExceptionHandlerExceptionResolver
支持的@ResponseStatus
+自定义异常 ;底层是ResponseStatusExceptionResolver
,把responsestatus注解的信息底层调用response.sendError(statusCode, resolvedReason);
tomcat发送的/errorSpring底层的异常,如 参数类型转换异常;**
DefaultHandlerExceptionResolver
处理框架底层的异常。**response.sendError(HttpServletResponse.**SC_BAD_REQUEST**, ex.getMessage());
自定义实现 HandlerExceptionResolver 处理异常,可以作为 默认的 全局异常处理规则
ErrorViewresolver 实现自定义处理异常
response.sendError();
error 请求就会转给 controller- 你的异常没有任何人能处理的时候,Tomcat 底层
response.sendError();
error 请求 就会转给 controller basicErrorController
要去的页面地址是ErrorViewResolver
异常处理自动配置原理
ErrorMvcAutoConfiguration 自动配置异常处理规则
容器中的组件 : 类型
DefaultErrorAttributes
-> id : errorAttributespublic class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver
DefaultErrorAttributes
: 定义错误页面中可以包含那些数据容器中的组件:类型:BasicErrorController –> id:basicErrorController(json+白页 适配响应)
- 处理默认 /error 路径的请求;页面响应 new ModelAndView(“error”, model);
- 容器中有组件 View->id是error;(响应默认错误页)
- 容器中放组件 BeanNameViewResolver(视图解析器);按照返回的视图名作为组件的id去容器中找View对象。
容器中的组件:类型:DefaultErrorViewResolver -> id:conventionErrorViewResolver
- 如果发生错误,会以HTTP的状态码 作为视图页地址(viewName),找到真正的页面
- error/404、5xx.html
异常处理步骤流程
1、执行目标方法,目标方法运行期间有任何异常都会被catch、而且标志当前请求结束;并且用 dispatchException
2、进入视图解析流程(页面渲染?)
processDispatchResult(processedRequest, response, mappedHandler, **mv**, **dispatchException**);
3、mv = processHandlerException;处理handler发生的异常,处理完成返回ModelAndView;
1、遍历所有的 handlerExceptionResolvers,看谁能处理当前异常【HandlerExceptionResolver处理器异常解析器
系统默认的 异常解析器;
- DefaultErrorAttributes先来处理异常。把异常信息保存到rrequest域,并且返回null;
- 默认没有任何人能处理异常,所以异常会被抛出
- 如果没有任何人能处理最终底层就会发送 /error 请求。会被底层的BasicErrorController处理
- 解析错误视图;遍历所有的 ErrorViewResolver 看谁能解析。
- 默认的 DefaultErrorViewResolver ,作用是把响应状态码作为错误页的地址,error/500.html
- 模板引擎最终响应这个页面 error/500.html
Web 原生组件注入(Servlet、Filter、Listener)
使用 Servlet 原生 API
@ServletComponentScan(basePackages="com.cs7eric.boot")
: 指定原生 Servlet 组件都放在那里@@WebServlet(urlPatterns = "/my")
: 效果 :直接响应, 没有经过 Spring 的 拦截器@WebFilter(urlPatterns = {"/css/*","/images/*"})
@WebListener
扩展 : DispatchServlet 如何注册进来
- 容器中 自动配置了 DispatcherServlet 属性绑定到 WebMvcProperties ; 对应的配置文件配置项是 spring.mvc
- 通过
ServletRegistrationBean<DispatcherServlet>
把 DispatcherServlet 配置进来 - 默认 映射的是
/
路径
Tomcat - Servlet :
多个 Servlet 都能处理 到同一层路径,精确优选原则
- A : /my/
- B : /my1
使用 RegistrationBean
ServletRegistrationBean 、FilterRegistrationBean 、 ServletListenerRegistrationBean
1 |
|
嵌入式 Servlet 容器
切换嵌入式 Servlet 容器
默认支持的 webServer
- Tomcat、Jetty、Undertow
ServletWebServerApplicationContext
容器启动寻找ServletWebServerFactory
并引导创建 服务器
切换服务器
```xml
org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-tomcat 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
<br>
- 原理
- - SpringBoot应用启动发现当前是Web应用。web场景包-导入tomcat
- web应用会创建一个web版的ioc容器 `ServletWebServerApplicationContext`
- `ServletWebServerApplicationContext` 启动的时候寻找 `ServletWebServerFactory (Servlet 的web服务器工厂---> Servlet 的web服务器)`
- SpringBoot底层默认有很多的WebServer工厂;`TomcatServletWebServerFactory`, `JettyServletWebServerFactory`, or `UndertowServletWebServerFactory`
- `底层直接会有一个自动配置类。ServletWebServerFactoryAutoConfiguration`
- `ServletWebServerFactoryAutoConfiguration导入了ServletWebServerFactoryConfiguration(配置类)`
- `ServletWebServerFactoryConfiguration 配置类 根据动态判断系统中到底导入了那个Web服务器的包。(默认是web-starter导入tomcat包),容器中就有 TomcatServletWebServerFactory`
- `TomcatServletWebServerFactory 创建出Tomcat服务器并启动;TomcatWebServer 的构造器拥有初始化方法initialize---this.tomcat.start();`
- `内嵌服务器,就是手动把启动服务器的代码调用(tomcat核心jar包存在)`
<br>
##### 定制 Servlet 容器
- 实现 **`WebServletFactoryCustomier<ConfiurabaleServletWebServerFactory>`**
- 把配置文件的值 和 `ServletWebServerFactory` 进行绑定
- 修改配置文件 server.xxx
- 直接自定义 **`ConfigurableServletWebServerFactory`**
<br>
**xxxxxCustomizer:定制化器,可以改变xxxx的默认规则**
```java
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.stereotype.Component;
@Component
public class CustomizationBean implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
@Override
public void customize(ConfigurableServletWebServerFactory server) {
server.setPort(9000);
}
}
定制化原理
定制化的常见方式
修改配置文件
xxxCustomizer
编写自定义的配置类,xxxConfiguration + @Bean 替换、增加容器中的默认组件;视图解析器
Web 应用配置类实现
WebMvcConfigurer
即可定制 web 功能; +@Bean
给容器中在拓展一些组件- ```java
@Configuration
public class AdminWebConfig implements WebMvcConfigurer1
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
- **`EnableWebMvc` + WebMvcConfigurer** -- **@Bean** 可以全面接管 SpringMVC,所有规则全部自己重新配置;实现定制和拓展功能
- 原理
- `WebMvcAutoConfiguration` 默认的 SpringMVC 的自动配置功能类,静态资源、欢迎页
- 一旦使用 `@EnableWebMvc` 会 `@Import(DelegatingWebMvcConfiguration.class)`
- **`DelegatingWebMvcConfiguration `** 的作用,只保证 SPringMVC的正常使用
- 把所有系统中的 WebMvcConfigurer 拿过来,所有功能的定制都是这些 WebMvcConfigurer 合起来一起生效
- 自动配置了一些 非常底层的组件。 `RequestMappingHandlerMapping` 这些组件依赖的组件都是从容器中获取
- `public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport`
- **`WebMvcAutoConfiguration`** 里面的配置要能生效
必须
**`@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)`**
- **`@EnableWebMvc`** 导致了 **WebMvcAutoConfiguation** 没有生效
<br>
##### 原理分析套路
**场景 starter - xxxxAutoConfiguration - 导入 xxx 组件 - 绑定 xxxProperties - 绑定配置文件项**
<br>
### 数据访问
---
#### SQL
##### 数据源的自动配置 - HikariDataSpurce
###### 导入 JDBC 场景
```xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
- ```java
数据库驱动
为什么导入 JDBC 场景,官方不导入 驱动
因为 官方不知道我们接下来要操作什么数据库
数据库版本和驱动版本对应
1 | 默认版本:<mysql.version>8.0.22</mysql.version> |
分析自动配置
自动配置的类
DataSourceAutoConfiguration : 数据源的自动配置
- 修改数据源的相关配置 :
spring.datasource
- **数据库连接池的配置,是自己容器中没有 DataSource 才自动配置的 **
- 底层配置好的连接池是 HikariDataSource
1
2
3
4
5
6
7
protected static class PooledDataSourceConfiguration
- 修改数据源的相关配置 :
DataSourceTransactionManagerAutoConfiguration : 事务管理器的自动配置
JdbcTemplateAutoConfiguration : JdbcTemplate的自动配置,可以来对数据库进行 CRUD
- 可以修改这个配置项 @ConfigurationProperties(prefix = “spring.jdbc”) 来修改 JdbcTemplate
- @Bean @Primary JdbcTemplate ; 容器中有这个组件
JndiDataSourceAutoConfiguration : jndi 的自动配置
XADataSourceAutoConfiguration : 分布式事务相关的
修改配置项
1 | spring: |
测试
1 |
|
使用 Druid 数据源
druid 官方 GitHub 地址
https://github.com/alibaba/druid
整合 第三方技术的两种方式
- 自定义
- 找 starter
自定义方式
创建数据源
1 | <dependency> |
StatViewServlet
StatViewServlet 的用途包括
- 提供监控信息展示的 html 页面
- 提供监控信息的 JSON API
1 | <servlet> |
StatFilter
用于统计监控信息,如 SQL监控,URI 监控
需要给数据源中配置如下属性 : 可以允许多个 filter,多个用, 分割
如:
<property name="filters" value="stat,slf4j" />
系统中所有filter:
别名 | Filter类名 |
---|---|
default | com.alibaba.druid.filter.stat.StatFilter |
stat | com.alibaba.druid.filter.stat.StatFilter |
mergeStat | com.alibaba.druid.filter.stat.MergeStatFilter |
encoding | com.alibaba.druid.filter.encoding.EncodingConvertFilter |
log4j | com.alibaba.druid.filter.logging.Log4jFilter |
log4j2 | com.alibaba.druid.filter.logging.Log4j2Filter |
slf4j | com.alibaba.druid.filter.logging.Slf4jLogFilter |
commonlogging | com.alibaba.druid.filter.logging.CommonsLogFilter |
慢 SQL 记录配置
1 | <bean id="stat-filter" class="com.alibaba.druid.filter.stat.StatFilter"> |
使用 官方 starter 方式
引入 druid-starter
1 | <dependency> |
分析自动配置
- 拓展 配置项 spring.datasource.druid
- DruidSpringAopConfiguration.class , 监控 SpringBean 配置的; 配置项 : spring.datasource.druid.aop-patterns
- DruidStatViewServletConfigurattion.class, 监控页的配置 : spring.datasource.druid.stat-view-servlet : 默认开启
- DruidWebStatFilterConfiguration.class, web 监控配置; spring.datasource.druid.web-stat-filter ; 默认开启
- DruidFilterConfiguration.class, 所有 Druid 自己 filter 的配置
1 | private static final String FILTER_STAT_PREFIX = "spring.datasource.druid.filter.stat"; |
配置示例
1 | server: |
SpringBoot 配置示例
https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter
配置项列表
整合 MyBatis 操作
官方文档
starter
- SpringBoot 官方的 Starter :**
spring-boot-starter-*
** - 第三方的 :
*-spring-boot-starter
1 | <dependency> |
配置模式
- 全局配置文件
- SqlSessionFactory : 自动配置好了
- SqlSession : 自动配置了 SqlSessionTemplate 组合了 SqlSession
@Import(AutoConfiguredMapperScannerRegistrar.class)
- Mapper : 只要我们写的操作 MyBatis 的接口标注了 @Mapper 就会被自动扫描进来
1 | : MyBatis配置项绑定类。 |
可以修改配置文件中 mybatis 开始的所有 (prefix = mybatis)
1 | # 配置mybatis规则 |
配置 private Configuration configuration; mybatis.configuration
下面的所有,就是相当于 改 mybatis 全局配置文件中的值
1 | # 配置mybatis规则 |
- 导入 mybatis 官方 starter
- 编写 mapper 接口。 标注 @Mapper 注解
- 编写 sql 映射文件 并绑定 mapper 接口
- 在 application.yaml 中指定 Mapper 配置文件的位置,以及指定全局配置文件的信息,(建议:配置在
mybatis.configuration
)
混合模式
1 |
|
最佳实战
- 引入 mybatis-starter
- 配置 application.yaml 中,指定 mapper-location 位置即可
- 编写 Mapper 接口 并标注 @Mapper 注解
- 简单方法直接注解方式
- 复杂方法 编写 mapper.xml 进行绑定映射
MapperScan("com.cs7eric.boot.mapper")
简化,其他的接口就可以不用 标注 @Mapper 注解
整合 MyBatis-Plus 完成 CRUD
什么是 MyBatis-Plus
MyBatis-Plus (简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
建议安装 MybatisX 插件
整合 MyBatis-Plus
1 | <dependency> |
自动配置
- MyBatisPlusAutoConfiguration 配置类,MyBatisPlusProperties 配置项绑定。 mybatis-plus: xxx 就是对 mybatis-plus 的定制
- SqlSessionFactory 自动配置好。底层就是容器中默认的数据源
- mapperLocations 自动配置好的。有 默认值。
classpath*:mapper/**/*.xml
; 任意包的路径下的所有 mapper 文件夹下任意路径下的所有 xml 都是 sql 映射文件。建议以后 sql 映射文件都放在 mapper 下 - 容器中也自动配置了 SqlSessionTemplate
- @Mapper 标注的接口也会被自动扫描;建议 直接 **
@MapperScan("com.cs7eric.boot.mapper")
** 批量扫描就行
优点
- 只需要我们的 Mapper 继承
BaseMapper
就可以 拥有 CRUD 能力
CRUD 功能
1 |
|
1 |
|
步骤:
- 页面发送请求,Thymeleaf 解析
- controller 做请求 “第一次” 处理
- service
- mapper
1 | <td> |
1 | /** |
1 | public interface UserService extends IService<User> { |
1 |
|
1 |
|
1 |
|
NoSQL
Redis 是一个 开源(BSD 许可) 的,内存中的数据结构 存储系统,它可以用作数据库,缓存 和 消息中间件。
它支持多种类型的数据结构,如 字符串(strings) , 散列(hashes) , 列表(lists) , 集合(sets) , 有序集合(sorted sets) 与范围查询, bitmaps , hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication) ,LUA脚本(Lua scripting) , LRU驱动事件(LRU eviction) ,事务(transactions) 和不同级别的 磁盘持久化(persistence) , 并通过 Redis哨兵(Sentinel) 和自动 分区(Cluster) 提供高可用性(high availability)。
Redis 自动配置
1 | <dependency> |
自动配置:
- RedisAutoConfiguration 自动配置类。RedisProperties 属性类 –>
spring.redis.xxx
是对 Redis 的配置 - 连接工厂是准备好的。 LettuceConnectionConfiguration、JedisConnectionConfiguration
- 自动注入了 RedisTemplate<Object,Object> : xxxTemplate;
- 自动注入了 StringRedisTemplate; k : v 都是 String
- key: value
- 底层只要我们使用了 StringRedisTemplate、RedisTemplate 就可以操作 Redis
redis 环境搭建
- 阿里云按量付费 redis, 经典网络
- 申请 Redis 的公网连接地址
- 修改 白名单 允许 0.0.0.0/0
RedisTemplate 与 Lettuce
1 |
|
切换至 jedis
1 | <dependency> |
1 | spring: |
单元测试
JUnit 5 的变化
SpringBoot 2.2.0 版本开始引入了 JUnit5 作为单元测试默认库
作为 最新版本的 JUnit 框架,JUnit5 与之前版本的 JUint 框架有很大的不同。由三个不同子项目的几个不同模块组成
JUnit = JUnit Platform + JUnit Jupiter + JUnit Vintage
JUnit Platform : Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入
JUnit Jupiter : JUnit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部 包含了一个测试引擎,用于在Junit Platform上运行。
**JUnit Vintage **: 由于JUint已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4.x,Junit3.x的测试引擎
注意:
SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖。如果需要兼容junit4需要自行引入(不能使用junit4的功能 @Test****)
JUnit 5’s Vintage Engine Removed from spring-boot-starter-test,如果需要继续兼容junit4需要自行引入vintage
1 | <dependency> |
1 | <dependency> |
现在版本:
1 |
|
以前:
@SpringBootTest + @RunWith(SpringTest.class)
SpringBoot 整合之后
- 编写 测试方法 : @Test 标注( 注意 需要使用 JUnit 5 版本的注解)
- JUnit 类具有 Spring 的功能,@Autowired, 比如
@Transactional
标注测试方法,测试完成后自动回滚
JUnit 5 常用注解
JUnit5的注解与JUnit4的注解有所变化
https://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations
- @Test : 表示方法是测试方法。但是与 Junit 4 不同,他的职责非常单一不能声明任何属性,拓展的测试将会由 Jupiter 提供额外测试
- @ParameterizedTest : 表示方法是参数化测试,下方会有详细介绍
- @RepeatedTest : 表示方法可重复执行,下方会有详细介绍
- @DisplayName : 为测试类或者测试方法设置展示名称
- @BeforeEach : 表示在每个单元测试之前执行
- @AfterEach : 表示在每个单元测试之后执行
- @BeforeAll : 表示在所有单元测试之前执行
- @AfterAll : 表示在所有单元测试之后执行
- @Tag : 表示单元测试类别,类似于 JUnit 4 中的 @Categories
- @Disabled : 表示测试类或者测试方法不执行,类似于 JUnit 4 中的 @Ignore
- @Timeout : 表示测试方法运行如果超过了指定时间将会返回 错误
- @ExtendWith : 为测试类或测试方法提供扩展类引用
1 | import org.junit.jupiter.api.Test; //注意这里使用的是jupiter的Test注解!! |
断言 assertions
断言 assertions 是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是 org.junit.jupiter.api.Assertions 的静态方法。 JUnit 5 内置的断言可以分成如下几个类别
检查业务逻辑返回的数据是否合理。
所有的测试运行结束以后,会有一个详细的测试报告
简单断言
用来对单个值进行简单的验证
方法 | 说明 |
---|---|
assertEquals | 判断两个对象或两个原始类型是否相等 |
assertNotEquals | 判断两个对象或两个原始类型是否不相等 |
assertSame | 判断两个对象引用是否指向同一个对象 |
assertNotSame | 判断两个对象引用是否指向不同的对象 |
assertTrue | 判断给定的布尔值是否为 true |
assertFalse | 判断给定的布尔值是否为 false |
assertNull | 判断给定的对象引用是否为 null |
assertNotNull | 判断给定的对象引用是否不为 null |
1 |
|
数组断言
通过 assertArrayEquals 方法来判断两个对象或原始类型的数组是否相等
1 |
|
组合断言
assertAll 方法接受多个 org.junit.jupiter.api.Executable 函数式接口的实例作为要验证的断言,可以通过 lambda 表达式很容易的提供这些断言
1 |
|
异常断言
在JUnit4时期,想要测试方法的异常情况时,需要用**@Rule注解的ExpectedException变量还是比较麻烦的。而JUnit5提供了一种新的断言方式Assertions.assertThrows()** ,配合函数式编程就可以进行使用。
1 |
|
超时断言
JUnit 5 还提供了 Assertions.assertTimeout() 为测试方法设置了超时时间
1 |
|
快速失败
通过 fail 方法直接使得测试失败
1 |
|
前置条件 assumptions
JUnit 5 中的前置条件(assumptions【假设】)类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止。前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要。
1 |
|
assumeTrue 和 assumFalse 确保给定的条件为 true 或 false,不满足条件会使得测试执行终止。assumingThat 的参数是表示条件的布尔值和对应的 Executable 接口的实现对象。只有条件满足时,Executable 对象才会被执行;当条件不满足时,测试执行并不会终止。
嵌套测试
JUnit 5 可以通过 Java 中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach 和@AfterEach 注解,而且嵌套的层次没有限制。
1 |
|
参数化测试
参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利。
利用**@ValueSource**等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。
@ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
@NullSource: 表示为参数化测试提供一个null的入参
@EnumSource: 表示为参数化测试提供一个枚举入参
@CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
@MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)
当然如果参数化测试仅仅只能做到指定普通的入参还达不到让我觉得惊艳的地步。让我真正感到他的强大之处的地方在于他可以支持外部的各类入参。如:CSV,YML,JSON 文件甚至方法的返回值也可以作为入参。只需要去实现ArgumentsProvider接口,任何外部文件都可以作为它的入参。
1 |
|
迁移指南
在进行迁移的时候需要注意如下的变化:
- 注解在 org.junit.jupiter.api 包中,断言在 org.junit.jupiter.api.Assertions 类中,前置条件在 org.junit.jupiter.api.Assumptions 类中。
- 把@Before 和@After 替换成@BeforeEach 和@AfterEach。
- 把@BeforeClass 和@AfterClass 替换成@BeforeAll 和@AfterAll。
- 把@Ignore 替换成@Disabled。
- 把@Category 替换成@Tag。
- 把@RunWith、@Rule 和@ClassRule 替换成@ExtendWith
指标监控
SpringBoot Actuator
简介
未来每一个微服务在云上部署以后,我们都需要对其进行监控、追踪、审计、控制等。SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能。
1 | <dependency> |
1.x 与 2.x 的不同
如何使用
- 引入场景
- 访问 http://localhost:8080/actuator/ **
- 暴露所有监控信息为HTTP
1 | management: |
- 测试
http://localhost:8080/actuator/beans
http://localhost:8080/actuator/configprops
http://localhost:8080/actuator/metrics
http://localhost:8080/actuator/metrics/jvm.gc.pause
http://localhost:8080/actuator/ endpointName/detailPath
可视化
https://github.com/codecentric/spring-boot-admin
Actuator Endpoint
最常使用的端点
ID | 描述 |
---|---|
auditevents |
暴露当前应用程序的审核事件信息。需要一个AuditEventRepository组件 。 |
beans |
显示应用程序中所有Spring Bean的完整列表。 |
caches |
暴露可用的缓存。 |
conditions |
显示自动配置的所有条件信息,包括匹配或不匹配的原因。 |
configprops |
显示所有@ConfigurationProperties 。 |
env |
暴露Spring的属性ConfigurableEnvironment |
flyway |
显示已应用的所有Flyway数据库迁移。 需要一个或多个Flyway 组件。 |
health |
显示应用程序运行状况信息。 |
httptrace |
显示HTTP跟踪信息(默认情况下,最近100个HTTP请求-响应)。需要一个HttpTraceRepository 组件。 |
info |
显示应用程序信息。 |
integrationgraph |
显示Spring integrationgraph 。需要依赖spring-integration-core 。 |
loggers |
显示和修改应用程序中日志的配置。 |
liquibase |
显示已应用的所有Liquibase数据库迁移。需要一个或多个Liquibase 组件。 |
metrics |
显示当前应用程序的“指标”信息。 |
mappings |
显示所有@RequestMapping 路径列表。 |
scheduledtasks |
显示应用程序中的计划任务。 |
sessions |
允许从Spring Session支持的会话存储中检索和删除用户会话。需要使用Spring Session的基于Servlet的Web应用程序。 |
shutdown |
使应用程序正常关闭。默认禁用。 |
startup |
显示由ApplicationStartup 收集的启动步骤数据。需要使用SpringApplication 进行配置BufferingApplicationStartup 。 |
threaddump |
执行线程转储。 |
如果您的应用程序是Web应用程序(Spring MVC,Spring WebFlux或Jersey),则可以使用以下附加端点:
ID | 描述 |
---|---|
heapdump |
返回hprof 堆转储文件。 |
jolokia |
通过HTTP暴露JMX bean(需要引入Jolokia,不适用于WebFlux)。需要引入依赖jolokia-core 。 |
logfile |
返回日志文件的内容(如果已设置logging.file.name 或logging.file.path 属性)。支持使用HTTPRange 标头来检索部分日志文件的内容。 |
prometheus |
以Prometheus服务器可以抓取的格式公开指标。需要依赖micrometer-registry-prometheus 。 |
最常用的Endpoint
- Health:监控状况
- Metrics:运行时指标
- Loggers:日志记录
Health Endpoint
健康检查端点,我们一般用于在云平台,平台会定时的检查应用的健康状况,我们就需要Health Endpoint可以为平台返回当前应用的一系列组件健康状况的集合。
重要的几点:
- health endpoint返回的结果,应该是一系列健康检查后的一个汇总报告
- 很多的健康检查默认已经自动配置好了,比如:数据库、redis等
- 可以很容易的添加自定义的健康检查机制
Metrics Endpoint
提供详细的、层级的、空间指标信息,这些信息可以被 pull(主动推送) 或者 push (被动获取) 方式得到
- 通过Metrics对接多种监控系统
- 简化核心Metrics开发
- 添加自定义Metrics或者扩展已有Metrics
管理 Endpoints
开启与禁用 Endpoints
- 默认所有的 Endpoint 除过 shutdown 都是开启的
- 需要开启或者禁用 某个 Endpoint。配置模式为 managerment.endpoint.
.enabled = true
1 | management: |
- 或者禁用所有的 Endpoint 然后手动开启指定的 Endpoint
1 | management: |
暴露 Endpoint
支持的暴露方式
- HTTP:默认只暴露health和info Endpoint
- JMX:默认暴露所有Endpoint
- 除过health和info,剩下的Endpoint都应该进行保护访问。如果引入SpringSecurity,则会默认配置安全访问规则
ID | JMX | Web |
---|---|---|
auditevents |
Yes | No |
beans |
Yes | No |
caches |
Yes | No |
conditions |
Yes | No |
configprops |
Yes | No |
env |
Yes | No |
flyway |
Yes | No |
health |
Yes | Yes |
heapdump |
N/A | No |
httptrace |
Yes | No |
info |
Yes | Yes |
integrationgraph |
Yes | No |
jolokia |
N/A | No |
logfile |
N/A | No |
loggers |
Yes | No |
liquibase |
Yes | No |
metrics |
Yes | No |
mappings |
Yes | No |
prometheus |
N/A | No |
scheduledtasks |
Yes | No |
sessions |
Yes | No |
shutdown |
Yes | No |
startup |
Yes | No |
threaddump |
Yes | No |
定制 Endpoint
定制 Health 信息
1 | import org.springframework.boot.actuate.health.Health; |
1 | management: |
1 |
|
定制 info 信息
常用两种方式
编写配置文件
- ```yaml
info:
appName: boot-admin
version: 2.0.1
mavenProjectName: @project.artifactId@ #使用@@可以获取maven的pom文件值
mavenProjectVersion: @project.version@1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- 编写 InfoContributor
- ```java
import java.util.Collections;
import org.springframework.boot.actuate.info.Info;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.stereotype.Component;
@Component
public class ExampleInfoContributor implements InfoContributor {
@Override
public void contribute(Info.Builder builder) {
builder.withDetail("example",
Collections.singletonMap("key", "value"));
}
}
- ```yaml
http://localhost:8080/actuator/info 会输出以上方式返回的所有info信息
定制 Metric 信息
SpringBoot 支持自动装适配的 Metrics
JVM metrics, report utilization of:
- Various memory and buffer pools
- Statistics related to garbage collection
- Threads utilization
- Number of classes loaded/unloaded
CPU metrics
File descriptor metrics
Kafka consumer and producer metrics
Log4j2 metrics: record the number of events logged to Log4j2 at each level
Logback metrics: record the number of events logged to Logback at each level
Uptime metrics: report a gauge for uptime and a fixed gauge representing the application’s absolute start time
Tomcat metrics (
server.tomcat.mbeanregistry.enabled
must be set totrue
for all Tomcat metrics to be registered)Spring Integration metrics
增加 定制 Metrics
```java
class MyService{
Counter counter;
public MyService(MeterRegistry meterRegistry){
counter = meterRegistry.counter(“myservice.method.running.counter”);
}
public void hello() {
counter.increment();
}
}//也可以使用下面的方式
@Bean
MeterBinder queueSize(Queue queue) {
return (registry) -> Gauge.builder(“queueSize”, queue::size).register(registry);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<br>
- 定制 Endpoint
- ```java
@Component
@Endpoint(id = "container")
public class DockerEndpoint {
@ReadOperation
public Map getDockerInfo(){
return Collections.singletonMap("info","docker started...");
}
@WriteOperation
private void restartDocker(){
System.out.println("docker restarted....");
}
}
场景 : 开发 ReadinessEndpoint 来管理程序是否就绪,或者 LivenessEndpoint 来管理程序是否存活
原理解析
Profile 功能
为了方便多环境适配,springboot 简化了 profile
application-profile 功能
- 默认配置文件 application.yaml 任何时候都会加载
- 指定环境配置文件 application-{env}.yaml
- 激活指定环境
- 配置文件激活
- 命令行激活 : java -jar xxx.jar –spring.profiles.active=prod –person.name = haha
- 修改配置文件的任意值,命令行优先
- 默认配置与环境配置同时生效
- 同名配置项,profile 配置优先
@Profile 条件装配功能
1 |
|
profile 分组
1 | spring.profiles.group.production[0]=proddb |
外部化配置
- Default properties (specified by setting
SpringApplication.setDefaultProperties
). @PropertySource
annotations on your@Configuration
classes. Please note that such property sources are not added to theEnvironment
until the application context is being refreshed. This is too late to configure certain properties such aslogging.*
andspring.main.*
which are read before refresh begins.- Config data (such as
**application.properties**
files) - A
RandomValuePropertySource
that has properties only inrandom.*
. - OS environment variables.
- Java System properties (
System.getProperties()
). - JNDI attributes from
java:comp/env
. ServletContext
init parameters.ServletConfig
init parameters.- Properties from
SPRING_APPLICATION_JSON
(inline JSON embedded in an environment variable or system property). - Command line arguments.
properties
attribute on your tests. Available on@SpringBootTest
and the test annotations for testing a particular slice of your application .@TestPropertySource
annotations on your tests.- Devtools global settings properties in the
$HOME/.config/spring-boot
directory when devtools is active.
外部配置源
常用 :Java 属性文件、YAML 文件、环境变量、命令行参数
配置文件查找位置
- classpath 根路径
- classpath 根路径下config目录
- jar包当前目录
- jar包当前目录的config目录
- /config子目录的直接子目录
配置文件加载顺序
- 当前jar包内部的application.properties和application.yml
- 当前jar包内部的application-{profile}.properties 和 application-{profile}.yml
- 引用的外部jar包的application.properties和application.yml
- 引用的外部jar包的application-{profile}.properties 和 application-{profile}.yml
指定环境优先,外部优先,后面的可以覆盖前面的同名配置项
自定义 starter
starter 启动原理
- starter-pom 引入 autoconfigurer 包
- autoconfigure 包中配置使用 META-INF/spring.factories 中的 EnableAutoConfiguration 的值,使得项目 启动加载指定的自动配置类
- 编写自动配置类 xxxAutoConfiguration -> xxxProperties
- @Configuration
- @Conditional
- EnableConfigurationProperties
- @Bean
- … …
引入 starter — xxxAutoConfiguration — 容器中的组件 — 绑定 xxxProperties — 配置项
自定义 starter
atguigu-hello-spring-boot-starter(启动器)
atguigu-hello-spring-boot-starter-autoconfigure(自动配置包)
SpringBoot 原理
Spring原理【Spring注解 】、SpringMVC原理、自动配置原理、SpringBoot原理
SpringBoot 启动过程
创建 SpringApplication
- 保存一些信息。
- 判定当前应用的类型。ClassUtils。Servlet
- bootstrappers****:初始启动引导器(List
):去spring.factories文件中找 org.springframework.boot.Bootstrapper - 找 ApplicationContextInitializer;去spring.factories****找 ApplicationContextInitializer
- List<ApplicationContextInitializer<?>> initializers
- 找 ApplicationListener ;应用监听器。去spring.factories****找 ApplicationListener
- List<ApplicationListener<?>> listeners
运行 SpringApplication
- StopWatch
- 记录应用的启动时间
- 创建引导上下文(Context环境)****createBootstrapContext()
- 获取到所有之前的 bootstrappers 挨个执行 intitialize() 来完成对引导启动器上下文环境设置
- 让当前应用进入headless模式。java.awt.headless
- 获取所有 RunListener****(运行监听器)【为了方便所有Listener进行事件感知】
- getSpringFactoriesInstances 去spring.factories****找 SpringApplicationRunListener.
- 遍历 SpringApplicationRunListener 调用 starting 方法;
- 相当于通知所有感兴趣系统正在启动过程的人,项目正在 starting。
- 保存命令行参数;ApplicationArguments
- 准备环境 prepareEnvironment();
- 返回或者创建基础环境信息对象。StandardServletEnvironment
- 配置环境信息对象。
- 读取所有的配置源的配置属性值。
- 绑定环境信息
- 监听器调用 listener.environmentPrepared();通知所有的监听器当前环境准备完成
- 创建IOC容器(createApplicationContext())
- 根据项目类型(Servlet)创建容器,
- 当前会创建 AnnotationConfigServletWebServerApplicationContext
- 准备ApplicationContext IOC容器的基本信息 prepareContext()
- 保存环境信息
- IOC容器的后置处理流程。
- 应用初始化器;applyInitializers;
- 遍历所有的 ApplicationContextInitializer 。调用 initialize.。来对ioc容器进行初始化扩展功能
- 遍历所有的 listener 调用 contextPrepared。EventPublishRunListenr;通知所有的监听器****contextPrepared
- 所有的监听器 调用 contextLoaded。通知所有的监听器 contextLoaded;
- 刷新IOC容器。refreshContext
- 创建容器中的所有组件(Spring注解)
- 容器刷新完成后工作?afterRefresh
- 所有监听 器 调用 listeners.started(context); 通知所有的监听器 started
- 调用所有runners;callRunners()
- 获取容器中的 ApplicationRunner
- 获取容器中的 CommandLineRunner
- 合并所有runner并且按照@Order进行排序
- 遍历所有的runner。调用 run 方法
- 如果以上有异常,
- 调用Listener 的 failed
- 调用所有监听器的 running 方法 listeners.running(context); 通知所有的监听器 running
- running如果有问题。继续通知 failed 。****调用所有 Listener 的 failed;****通知所有的监听器 failed
1 | public interface Bootstrapper { |
1 |
|
1 |
|
Application Events and Listeners
ApplicationContextInitializer
ApplicationListener
SpringApplicationRunListener
ApplicationRunner 与 CommandLineRunner
- Title: springboot2
- Author: cccs7
- Created at: 2022-12-24 13:31:12
- Updated at: 2023-06-29 23:14:51
- Link: https://blog.cccs7.icu/2022/12/24/springboot/
- License: This work is licensed under CC BY-NC-SA 4.0.