前言
- 本文来源于【尚硅谷】SpringBoot2零基础入门教程(spring boot2干货满满)_哔哩哔哩_bilibili
- 学习过雷神的朋友肯定知道,雷神的笔记非常的随性,所以本文对视频内容进行提炼并且二次整理
- 由于作者做笔记时图片使用的是本地路径【MarkDown语法你懂的】所以本文博客中图片无法正常显示,如果有需求可以下载文章上方的pdf
- ps:作者正在学习雷神的Spring Boot3,后续会更新相关的笔记
基础入门
基础概念
-
简介
-
SpringBoot是整合Spring技术栈的一站式框架,是简化Spring技术栈的快速开发脚手架
-
-
优点
-
创建独立Spring应用
-
内嵌web服务器
-
自动starter依赖,简化构建配置
-
自动配置Spring以及第三方功能
-
提供生产级别的监控、健康检查及外部化配置
-
无代码生成、无需编写XML
-
-
缺点
-
人称版本帝,迭代快,需要时刻关注变化
-
封装太深,内部原理复杂,不容易精通(底层是spring)
-
时代背景
-
微服务
-
是一种架构风格
-
一个应用拆分为一组小型服务
-
每个服务运行在自己的进程内,也就是可独立部署和升级
-
服务之间使用轻量级HTTP交互
-
服务围绕业务功能拆分
-
可以由全自动部署机制独立部署
-
去中心化,服务自治(服务可以使用不同的语言、不同的存储技术)
-
-
分布式
-
分布式的困难
-
远程调用、服务发现、负载均衡、服务容错、配置管理
-
服务监控、链路追踪、日志管理、任务调度....
-
-
分布式解决:springboot+springcloud
-
-
云原生:原生应用上云
-
困难
-
服务自愈、弹性伸缩、服务隔离
-
自动化部署、灰度发布、流量治理...
-
-
入门
-
官方文档大致目录结构
-
使用步骤
-
导入依赖
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.4.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId><!--web场景启动器--> </dependency> </dependencies>
-
创建主程序
-
@SpringBootApplication标识该类是一个SpringBoot应用,称为主程序类(所有启动的入口)
-
命名无要求,其他的就是固定写法
-
@SpringBootApplication public class MainApplication { public static void main(String[] args) { SpringApplication.run(MainApplication.class,args); } }
-
测试业务
-
@RestController包含@RestquestBody和@Controller
-
业务要放在主程序对应的目录里面
-
@RestController public class HelloController { @RequestMapping("/hello") public String handle01(){ return "Hello, Spring Boot 2!"; } }
-
简化配置,创建application.properties,修改相应配置
-
文件名是固定的
-
放在resources目录下
-
如果pom.xml中的打包方式是pom会扫描不到!
-
-
简化部署
-
把项目打成jar包,直接在目标服务器执行即可
-
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
-
!!!自动配置原理
springboot的特点
依赖管理
-
父项目做依赖管理:几乎声明了所有开发中常用的依赖的版本号,自动版本仲裁机制
-
开发导入starter场景启动器
-
作用:只要引入starter,这个场景的所有常规需要的依赖都自动引入(依赖传递原则)
-
spring-boot-starter-* :官方场景启动器的命名规范,*代表某种场景
-
*-spring-boot-starter:第三方提供的简化开发的场景启动器
-
-
所有场景启动器最底层的依赖
-
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>2.3.4.RELEASE</version> <scope>compile</scope> </dependency>
-
自动版本仲裁,无需关注版本号
-
引入依赖默认可以不用写版本号
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>
-
但如果所需要的版本不在spring-boot-dependencies声明,就需要写版本号
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.34</version> </dependency>
-
-
可以修改默认版本号
-
查看spring-boot-dependencies里面规定当前依赖的版本,并找到标签名
先进入spring-boot-starter-parent再进入spring-boot-dependencies
-
在当前项目里面重写配置(就近优先原则,当前项目配置了就用当前的,否则使用父项目)
<properties> <mysql.version>5.1.43</mysql.version> </properties>
-
自动配置
-
自动配好Tomcat
-
引入Tomcat依赖
-
配置Tomcat
-
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <version>2.3.4.RELEASE</version> <scope>compile</scope> </dependency>
-
自动配好SpringMVC
-
引入SpringMVC全套组件
-
自动配好SpringMVC常用组件(功能)
-
-
自动配好Web常用功能(如字符编码功能)
-
默认的包结构
-
主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来
-
无需配置包扫描
-
想要改变扫描路径
-
在主程序中加入@SpringBootApplication(scanBasePackages="扫描的目录")
-
或者@ComponentScan 指定扫描路径
-
-
@SpringBootApplication("com.atguigu.boot") 等同于 @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan("com.atguigu.boot")
-
各种配置拥有默认值
-
默认配置最终都是映射到某个类上
-
配置文件的值最终会绑定每个类上,这个类会在容器中创建对象
-
-
按需加载所有自动配置项
-
只有引入的开发场景才会开启相应的自动配置
-
SpringBoot所有的自动配置功能都在spring-boot-autoconfigure包里面
-
容器功能
组件添加
@Configuration
-
作用:标识的类作为配置类,等同于配置文件
-
配置类里面使用@Bean标注在方法上给容器注册组件,默认是单实例的
-
配置类本身也是组件
-
属性
-
proxyBeanMethods:代理bean的方法
-
proxyBeanMethods = true【总会检查组件是否在容器中,保证每个@Bean方法返回的组件都是单实例的,full全配置】
-
proxyBeanMethods = false【每个@Bean方法返回的组件都是新创建的,不会保存代理对象,lite轻量级配置】
-
应用场景:配置类组件之间
-
无依赖关系用Lite模式加速容器启动过程,减少判断
-
有依赖关系,方法会被调用得到之前单实例组件,用Full模式
-
-
-
@bean
-
作用:给容器中添加组件,标识在方法上
-
默认以方法名作为组件的id,返回类型就是组件类型,返回的值就是组件在容器中的实例
-
也可以自定义组件id,通过@Bean("id")设置
@import
-
值是class类型的数组,作用:自动创建出对应类型的组件交给容器管理,默认组件的名字是全类名
-
标识在组件上
@Conditional
-
作用:条件装配,满足Conditional指定的条件,则进行组件注入
@ImportResource
-
作用:引入原生spring配置文件,属性值声明配置文件的位置
配置绑定
-
读取到properties文件中的内容,并且把它封装到JavaBean中
-
方式
-
@Component + @ConfigurationProperties
-
标识到具体组件(JavaBean)上
-
@Component 将组件交给容器管理,只有在容器中的组件才会拥有SpringBoot提供的功能
-
@ConfigurationProperties中的prefix属性可以指定配置文件中的前缀,根据该前缀下的属性和组件中的属性一一绑定
-
注意:!!!前缀不能有大写字母
-
-
-
@EnableConfigurationProperties + @ConfigurationProperties
-
@EnableConfigurationProperties 要标识到配置类上,属性是需要绑定配置的class,该注解有两个作用
-
开启对应组件的配置绑定功能
-
将对应组件注册到容器中
-
-
-
自动配置原理入门
引导加载自动配置类
@SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication{}
-
@SpringBootConfiguration其中包含@Configuration,代表当前是一个配置类
-
@EnableAutoConfiguration包含
-
@AutoConfigurationPackage:指定了默认包规则
-
利用Registrar给容器中导入一系列组件
-
将指定的主程序所在包下的所有组件导入进来
@Import(AutoConfigurationPackages.Registrar.class) public @interface AutoConfigurationPackage {}
-
-
@Import(AutoConfigurationImportSelector.class)
-
利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件
-
调用getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类
-
利用工厂加载 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件
-
从META-INF/spring.factories位置来加载一个文件
-
默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
-
spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories,里面包含了127个场景自动配置,所有自动配置启动的时候默认全部加载
-
-
-
按需开启自动配置项
-
按照条件装配规则(@Conditional),最终会按需配置(只有条件生效的时候配置才会生效)
-
具体例子
@Bean @ConditionalOnBean(MultipartResolver.class) //容器中有这个类型组件 @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) //容器中没有这个名字 multipartResolver 的组件 public MultipartResolver multipartResolver(MultipartResolver resolver) { return resolver; }
-
该方法作用:给容器添加文件上传解析器,并保证名字为multipartResolver
-
给@Bean标注的方法传入了对象参数,这个参数的值就会从容器中找
-
防止有些用户配置的文件上传解析器不符合规范
-
Detect if the user has created a MultipartResolver but named it incorrectly
-
-
由于@Bean标识的方法默认返回的组件是以方法名命名的,最后回向容器注入名为multipartResolver的文件上传解析器
-
-
SpringBoot默认会在底层配好所有的组件,但是如果用户自己配置了以用户的优先
修改默认配置
-
找到对应组件所绑定的配置文件的值(前缀),然后在application.properties进行修改/配置
==》
==》
总结
-
SpringBoot先加载所有的自动配置类(xxxxxAutoConfiguration)
-
每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值(xxxxProperties中拿,xxxProperties和配置文件进行了绑定)
-
生效的配置类就会给容器中装配很多组件(只要容器中有这些组件,相当于这些功能就有了)
-
xxxxxAutoConfiguration ---导入--> 组件 ---取值-->xxxxProperties ---绑定---> application.properties
-
-
定制化配置的方式
-
直接使用@Bean替换底层的组件
-
在application.properties中进行修改/配置
-
最佳实践
-
引入场景依赖
-
查看自动配置
-
自己分析,引入场景对应的自动配置一般都生效了
-
配置文件中配置debug=true开启自动配置报告
-
Negative(不生效)
-
Positive(生效)
-
-
-
是否需要修改
-
修改配置项
-
自己分析,xxxxProperties绑定了配置文件的哪些前缀
-
-
自定义加入或者替换组件
-
@Bean、@Component
-
使用自定义器 XXXXXCustomizer
-
开发技巧
lomback
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>
-
作用:帮类在编译的时候生成get、set、toString方法和构造器等等,简化javaBean开发
-
使用(注解标识到类上)
-
@Data:生成已有属性的get、set方法
-
@ToString:生成toString方法
-
@AllArgsConstructor:生成所有属性的有参构造器
-
@NoArgsConstructor:生成无参构造器
-
-
简化日志开发
-
@Slf4j:注入一个日志类,通过log日志记录器记录日志
-
@RestController @Slf4j public class HelloController { @RequestMapping("/hello") public String sayHello(){ log.error("测试日志记录器"); return "hello"; } }
dev-tools
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency>
-
作用:项目或者页面修改以后:使用
,就不用重启项目,保留控制台信息
Spring Initailizr(项目初始化向导)
-
作用
-
自动依赖引入
-
自动创建项目结构
-
自动编写好主配置类
-
-
使用
-
创建项目时选择Spring Initailizr
-
选择需要用到的开发场景
-
!!!核心功能
配置文件
-
分类
-
properties,用法之前提过
-
yaml,下面展开
-
yaml
简介
-
YAML 是 "YAML Ain't Markup Language"(YAML 不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:"Yet Another Markup Language"(仍是一种标记语言)
-
非常适合用来做以数据为中心的配置文件
基本语法
-
key: value; kv之间有空格
-
大小写敏感
-
使用缩进表示层级关系,缩进不允许使用tab,只允许空格,缩进的空格数不重要,只要相同层级的元素左对齐即可
-
#表示注释
-
字符串无需加引号,如果要加,'单引号'与"双引号"
-
表示字符串内容会被转义/不转义
-
也可以理解为能/不能输出转义字符\(例如'\n'输出\n "\n"输出换行)
-
数据类型的表示
-
字面量:单个的、不可再分的值。date、boolean、string、number、null
k: v
-
对象:键值对的集合(map、hash、set、object )
k: {k1:v1,k2:v2,k3:v3} #行内写法 #或 k: k1: v1 k2: v2 k3: v3
-
数组:一组按次序排列的值(array、list、queue)
k: [v1,v2,v3] #行内写法 #或者 k: - v1 - v2 - v3
-
创建application.yaml文件,application.properties和application.yaml两个文件中的配置组合起来生效,优先properties
举例
-
实体类
@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; }
-
application.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}]
配置提示
-
自定义的类和配置文件绑定一般没有提示,为开发方便可以导入
-
使用
-
导入依赖,然后重新启动项目,之后就有自定义类中的属性的配置提示啦
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>
-
在配置文件中,原属性名如果为驼峰命名,除首字母之外为大写字母的情况,其它大写字母会替代为 -小写字母
==>
-
-
在plugin中配置该消息,作用是项目打包时不将配置提示的处理器打包
<configuration> <excludes> <exclude> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> </exclude> </excludes> </configuration>
-
!!!web开发
SpringMVC自动配置概览
-
大多场景我们都无需自定义配置,springboot支持以下功能
-
内容协商视图解析器和BeanName视图解析器
-
静态资源(包括webjars)
-
自动注册
Converter,GenericConverter,Formatter
(转化器、格式化器) -
支持
HttpMessageConverters
(后来我们配合内容协商理解原理) -
自动注册
MessageCodesResolver
(国际化用,本课程不讲解) -
静态index.html 页支持
-
自定义
Favicon
(网站的小图标) -
自动使用
ConfigurableWebBindingInitializer
(DataBinder负责将请求数据绑定到JavaBean上)
-
-
定制化的建议
-
不用@EnableWebMvc注解。使用@Configuration+WebMvcConfigurer自定义规则
-
声明WebMvcRegistrations 改变默认底层组件
-
使用@EnableWebMvc+@Configuration+DelegatingWebMvcConfiguration 全面接管SpringMVC
-
简单功能分析
静态资源访问
静态资源目录
-
只要静态资源放在类路径下的
/static
or/public
or/resources
or/META-INF/resources
目录下(这四个目录时默认的静态资源文件夹)就可以通过当前项目根路径/ + 静态资源名访问到对应的静态资源
-
原理: 静态映射
/**
,请求进来,先去找Controller看能不能处理。不能处理的所有请求都交给静态资源处理器。静态资源也找不到则响应404页面 -
改变默认的静态资源文件夹
resources: static-locations: [classpath:/haha/] #可以配置多个
静态资源访问前缀
-
默认无前缀
-
改变默认的静态资源路径
spring: mvc: static-path-pattern: /res/**
-
访问方式:当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找
-
作用:指定前缀,防止被拦截器拦截
-
webjar
-
相关web的jar包可以去官网找WebJars - Web Libraries in Jars
-
导入jar包之后会自动映射 /webjars/**
-
-
访问地址:http://localhost:8080/webjars/jquery/3.5.1/jquery.js 后面地址要按照依赖里面的包路径
欢迎页支持
-
静态资源路径下放index.html,就会作为欢迎页使用
-
可以配置静态资源路径
-
不可以配置静态资源的访问前缀,否则导致 index.html不能被默认访问
-
-
使用controlller处理/index
自定义 Favicon
-
favicon.ico放在静态资源目录下即可
-
命名是固定的
-
不可以配置静态资源的访问前缀
-
静态资源配置原理
-
SpringBoot启动默认加载 xxxAutoConfiguration 类(自动配置类)
-
SpringMVC功能的自动配置类大多集中在WebMvcAutoConfiguration
-
生效条件
@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 {}
-
容器中包含的组件
-
配置类中的属性的绑定
-
配置类只有一个有参构造器,有参构造器中所有参数的值都会从容器中确定
//ResourceProperties resourceProperties;获取和spring.resources绑定的所有的值的对象 //WebMvcProperties mvcProperties 获取和spring.mvc绑定的所有的值的对象 //ListableBeanFactory beanFactory Spring的beanFactory //HttpMessageConverters 找到所有的HttpMessageConverters //ResourceHandlerRegistrationCustomizer 找到 资源处理器的自定义器。========= //DispatcherServletPath 处理路径相关 //ServletRegistrationBean 给应用注册Servlet、Filter.... public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties,ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider, ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider, ObjectProvider<DispatcherServletPath> dispatcherServletPath, ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) { this.resourceProperties = resourceProperties; this.mvcProperties = mvcProperties; this.beanFactory = beanFactory; this.messageConvertersProvider = messageConvertersProvider; this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable(); this.dispatcherServletPath = dispatcherServletPath; this.servletRegistrations = servletRegistrations; }
-
-
资源处理的默认规则
@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { if (!this.resourceProperties.isAddMappings()) { //判断是否禁用的静态资源,配置add-mappings等于false为禁用,默认不禁用 logger.debug("Default resource handling disabled"); return; } Duration cachePeriod = this.resourceProperties.getCache().getPeriod();//配置缓存策略 CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl(); if (!registry.hasMappingForPattern("/webjars/**")) { //webjars访问规则 customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**") .addResourceLocations("classpath:/META-INF/resources/webjars/") .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl)); } String staticPathPattern = this.mvcProperties.getStaticPathPattern();//静态资源路径配置规则 if (!registry.hasMappingForPattern(staticPathPattern)) { customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern) .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations())) /*rivate static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/" };*/ .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl)); } }
欢迎页的处理规则
//HandlerMapping:处理器映射。保存了每一个Handler能处理哪些请求 //WelcomePageHandlerMapping:即欢迎页能处理的请求映射规则 @Bean public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) { WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping( new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(), this.mvcProperties.getStaticPathPattern()); welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider)); welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations()); return welcomePageHandlerMapping; } WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders, ApplicationContext applicationContext, Optional<Resource> welcomePage, String staticPathPattern) { if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) { //要用欢迎页功能,必须是/**,所以不能有前缀 logger.info("Adding welcome page: " + welcomePage.get()); setRootViewName("forward:index.html"); } else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) { // 调用Controller /index logger.info("Adding welcome page template: index"); setRootViewName("index"); } }
请求参数处理
请求映射
rest使用与原理
-
可以使用合成注解@xxxMapping(xxx=Get、Post、Delete、Put)
-
Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)
-
以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户
-
现在: /user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
-
使用(原生表单)
-
配置核心Filter:HiddenHttpMethodFilter,要在SpringBoot中手动开启页面表单的Rest功能
spring: mvc: hiddenmethod: filter: enabled: true
-
表单设置method=post,设置隐藏域 _method=具体的请求方式put/delete...
-
-
扩展:自定义_method的名称
@Bean public HiddenHttpMethodFilter hiddenHttpMethodFilter(){ HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter(); methodFilter.setMethodParam("_m"); return methodFilter; }
-
-
Rest原理(表单提交要使用REST的时候)
-
假设表单提交带上_method=PUT
-
请求过来被HiddenHttpMethodFilter拦截
-
检查请求正常,并且是POST
-
获取到_method的值,然后将其全部转换成大写(说明_method的值大小写不敏感)
-
兼容这些请求:PUT,DELETE,PATCH
-
-
原生表单发送request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值
-
过滤器链放行的时候用wrapper,以后的方法调用getMethod是requesWrapper的
-
-
-
-
Rest使用客户端工具,如PostMan直接发送Put、delete等方式请求,无需Filter
请求映射原理
-
SpringMVC功能都从 org.springframework.web.servlet.DispatcherServlet---》doDispatch()进行分析
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); mappedHandler = getHandler(processedRequest);//找到当前请求使用哪个Handler(Controller的方法)处理
-
SpringBoot自动配置以下处理器映射
-
欢迎页的 WelcomePageHandlerMapping,访问 /能访问到index.html
-
默认的RequestMappingHandlerMapping,保存了所有@RequestMapping 和handler的映射规则
-
我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping(自定义 HandlerMapping)
-
-
所有的请求映射都在HandlerMapping中
-
请求进来,遍历所有的HandlerMapping看是否有请求信息
-
如果有就找到这个请求对应的handler
-
如果没有就继续遍历下一个 HandlerMapping
-
-
-
我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping
普通参数与基本注解
注解
-
@PathVariable、@RequestHeader、@RequestAttribute
-
@RequestParam、@MatrixVariable、@RequestBody、@CookieValue
-
@CookieValue标识在字符串类型参数可以获取Cookie的值,标识在Cookie类型参数可以获取Cookie对象
-
获取请求域属性两种方式
-
使用原生HttpServletRequest对象,调用getAttribute()方法
-
使用@RequestAttribute绑定属性
-
-
地址发送参数的方式
-
queryString(查询字符串):/car/{path}?xxx=xxx&aaa=ccc
-
矩阵变量:/cars/sell;low=34;brand=byd,audi,yd或者/cars/sell;low=34;brand=byd;brand=audi;brand=yd
-
矩阵变量必须有url路径变量才能被解析
-
控制器方法接收请求路径 /car/{path}
-
path代表分号前的路径
-
矩阵变量通过@MatrixVariable进行绑定
-
-
多个路径变量的相同参数的使用,如/boss/1;age=20/2;age=10
-
@MatrixVariable指定pathVar匹配路径变量,如果不指定则绑定第一个同名参数
-
-
SpringBoot默认是禁用了矩阵变量的功能,手动开启
-
配置类中实现WebMvcConfigurer接口,重写configurePathMatch方法
-
使用@Bean配置组件,返回WebMvcConfigurer接口对象
-
原理:对于路径的处理是使用UrlPathHelper进行解析,其中有个removeSemicolonContent属性(移除分号内容)默认为true,即不支持矩阵变量的
-
-
-
视图解析与模板引擎
视图解析
-
视图解析:
-
返回值以 forward: 开始: new InternalResourceView(forwardUrl);
-
request.getRequestDispatcher(path).forward(request, response); 底层是原生的转发
-
返回值以 redirect: 开始: new RedirectView()
-
response.sendRedirect(encodedURL)底层是原生的重定向
-
-
返回值是普通字符串: new ThymeleafView()
-
-
-
视图解析原理流程
-
目标方法处理的过程中,所有数据都会被放在 ModelAndViewContainer 里面(包括数据和视图地址)
-
方法的参数是一个自定义类型对象(用于接收数据的实体类,其值从请求参数中确定的),会放入ModelAndViewContainer
-
请求过程中产生的值都会放到该容器对象中
-
-
-
任何目标方法执行完成以后都会返回ModelAndView(数据和视图地址)
-
processDispatchResult处理派发结果(页面改如何响应)
-
调用render(mv, request, response) 进行页面渲染逻辑
-
根据方法的String返回值得到View对象【定义了页面的渲染逻辑】
-
遍历所有的视图解析器,尝试当前解析器是否能根据当前返回值得到View对象
-
ContentNegotiationViewResolver包含了下面所有的视图解析器,内部还是利用下面所有视图解析器得到视图对象
-
根据 redirect:/main.html 得到了RedirectView对象(Thymeleaf new RedirectView())
-
view.render(mv.getModelInternal(), request, response)视图对象调用自定义的render进行页面渲染工作
5. RedirectView重定向到一个页面 1. 获取目标url地址 2. 底层调用原生response.sendRedirect(encodedURL)进行重定向
模板引擎
Thymeleaf基础知识
-
SpringBoot默认不支持 JSP,需要引入第三方模板引擎技术实现页面渲染
-
Thymeleaf是现代化、服务端Java模板引擎
-
基本语法
-
表达式
表达式名字 语法 用途 变量取值 ${...} 获取请求域、session域、对象等值 选择变量 *{...} 获取上下文对象值 消息 #{...} 获取国际化等值 链接 @{...} 生成链接,括号里面是跳转的地址,使用绝对路径的时候会拼接当前项目路径 片段表达式 ~{...} jsp:include 作用,引入公共页面片段 -
thymeleaf使用
-
引入Starter
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
-
自动配置策略
-
所有thymeleaf的配置值都在 ThymeleafProperties
-
配置了
-
SpringTemplateEngine模板解析器
-
ThymeleafViewResolver视图解析器
public static final String DEFAULT_PREFIX = "classpath:/templates/";//视图文件放置的位置 public static final String DEFAULT_SUFFIX = ".html"; //xxx.html
-
-
-
页面开发
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> 导入thymeleaf的名称空间,之后使用thymeleaf语法就会有提示 <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1 th:text="${msg}">哈哈</h1> <h2> <a href="www.atguigu.com" th:href="${link}">去百度</a> <br/> <a href="www.atguigu.com" th:href="@{link}">去百度2</a> </h2> </body> </html>
构建后台管理系统
-
防止表单重复提交可以使用重定向,因为转发的话地址栏不变(地址还在登录页面,刷新就会重复提交)
-
检查登录状态,只有登录成功才可以访问
-
表单提交用post,发送login请求,表单提交用户名和密码的name与实体类中的属性一致
<form class="form-signin" th:action="@{/login}" method="post">表单提交的地址 <div class="form-signin-heading text-center"> <h1 class="sign-title">Sign In</h1> <img src="images/login-logo.png" alt=""/> </div> <div class="login-wrap"> <label style="color: red" th:text="${msg}"></label>错误信息显示 <input type="text" name="username" class="form-control" placeholder="User ID" autofocus>用户名 <input type="password" name="password" class="form-control" placeholder="Password">密码
-
通过检查session域中的是否有用户数据判断登录状态
-
@Controller public class controller { @RequestMapping({"/","login"}) public String login(){ return "login"; } @PostMapping("login") public String checkStatus(User user, HttpSession session, Model model){ if(StringUtils.hasLength(user.getUsername())&&"123456".equals(user.getPassword())){ session.setAttribute("loginUser",user); return "redirect:/index.html";//登录成功重定向到index.html,防止重复提交 }else { model.addAttribute("msg","账号或者密码错误"); return "login"; } } @RequestMapping("index.html") public String toIndex(HttpSession session,Model model){ if(session.getAttribute("loginUser")!=null){ return "index"; }else { model.addAttribute("msg","请重新登录"); return "login"; } } }
-
登录成功显示当前用户信息
-
使用thymeleaf行内写法
<a href="#" class="btn btn-default dropdown-toggle" data-toggle="dropdown"> <img src="images/photos/user-avatar.png" alt="" /> [[${session.loginUser.username}]] <span class="caret"></span> </a>
-
-
模板抽取
-
公共的代码可以抽取出来放在一个html中
-
所有资源涉及到公共代码的部分通过th:insert/replace/include引入公共代码块
-
th:insert/replace/include区别
<body> <div th:insert="footer :: copy"></div> <div th:replace="footer :: copy"></div> <div th:include="footer :: copy"></div> </body> 效果如下 <body> <div> <footer> © 2011 The Good Thymes Virtual Grocery </footer> </div> <footer> © 2011 The Good Thymes Virtual Grocery </footer> <div> © 2011 The Good Thymes Virtual Grocery </div> </body>
-
拓展功能
拦截器
创建拦截器
-
实现HandlerInterceptor接口
@Slf4j public class LoginInterceptor implements HandlerInterceptor { //目标方法执行之前 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String requestURI = request.getRequestURI(); log.info("preHandle拦截的请求路径是{}",requestURI); //登录检查逻辑 HttpSession session = request.getSession(); Object loginUser = session.getAttribute("loginUser"); if(loginUser != null){ return true;//放行 } //拦截住未登录,跳转到登录页 request.setAttribute("msg","请先登录"); request.getRequestDispatcher("/").forward(request,response); //这里使用转发是因为:重定向不能在request域中存错误信息,如果在session存错误信息,未登录的时候无法得到session中数据 return false; } }
配置拦截器
-
拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)
-
指定拦截规则
-
如果是拦截所有,静态资源也会被拦截,解决方式
-
配置静态资源访问前缀
-
放行静态资源的相关文件夹
-
@Configuration public class AdminWebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns("/**") //所有请求都被拦截包括静态资源 .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); //放行的请求 } }
拦截器原理
-
根据当前请求,找到HandlerExecutionChain【可以处理请求的handler以及handler的所有拦截器】
-
此图中拦截器链中的LonginInterceptor为我们自己定义的拦截器
-
-
先顺序执行 所有拦截器的preHandle方法
-
返回为true。则执行下一个拦截器的preHandle
-
返回为false。直接倒序执行所有已经执行了的拦截器的afterCompletion
-
-
如果任何一个拦截器返回false,直接跳出不执行目标方法
-
所有拦截器都返回True,执行目标方法
-
倒序执行所有拦截器的postHandle方法
-
-
前面的步骤有任何异常都会直接倒序触发afterCompletion
-
页面成功渲染完成以后,也会倒序触发afterCompletion
文件上传
使用
-
表单
<form method="post" action="/upload" enctype="multipart/form-data"> <input type="file" name="file"><br> <input type="submit" value="提交"> </form>
-
配置文件上传参数,前缀为spring.servlet.multipart
spring.servlet.multipart.max-file-size=10MB 单个文件最大大小 spring.servlet.multipart.max-request-size=100MB 最大请求大小(总共能传多大的文件)
-
上传代码
-
MutipartFile自动封装上传过来的文件
-
transferTo底层使用了FileCopyUtils工具类(文件复制工具类)
-
@PostMapping("/upload") public String upload(@RequestParam("email") String email, @RequestParam("username") String username, @RequestParam("headerImg") MultipartFile headerImg, @RequestParam("photos") MultipartFile[] photos) throws IOException { log.info("上传的信息:email={},username={},headerImg={},photos={}", email,username,headerImg.getSize(),photos.length); if(!headerImg.isEmpty()){ //保存到文件服务器,OSS服务器 String originalFilename = headerImg.getOriginalFilename(); headerImg.transferTo(new File("D:\\Study\\test\\"+originalFilename)); } if(photos.length > 0){ for (MultipartFile photo : photos) { if(!photo.isEmpty()){ String originalFilename = photo.getOriginalFilename(); photo.transferTo(new File("D:\\Study\\test\\"+originalFilename)); } } } return "index"; }
上传原理
-
文件上传自动配置类MultipartAutoConfiguration
-
自动配置好了 StandardServletMultipartResolver【文件上传解析器】
-
-
步骤
-
-
请求进来使用文件上传解析器判断(isMultipart)请求的内容类型是否为multipat,是的话通过resolveMultipart方法将原请求封装为MultipartHttpServletRequest【文件上传请求对象】并返回,
-
-
-
参数解析器来解析请求中的文件内容封装成MultipartFile
-
-
-
将request中文件信息封装为一个Map(MultiValueMap<String, MultipartFile>)
-
异常处理
默认规则
-
Spring Boot提供
/error
处理所有错误的映射 -
对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息
-
对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据
-
自定义异常界面
-
添加error的视图组件view
-
要完全替换默认行为,可以
-
实现
ErrorController
并注册该类型的Bean定义
-
-
添加
ErrorAttributes类型的组件
以使用现有机制但替换其内容 -
!!!静态资源/模板引擎目录下的error目录下的4xx,5xx页面会被自动解析!!!
-
404匹配404页面(精确匹配)
-
5xx匹配所有状态码为5开头的页面,4xx同理
-
-
定制错误处理逻辑
-
自定义错误页(error/404.html error/5xx.html)
-
有精确的错误状态码页面就匹配精确
-
没有就找4xx.html(小拓展:不带请求参数或者请求参数类型不对就报400错误)
-
如果都没有就触发白页
-
-
@ControllerAdvice+@ExceptionHandler处理全局异常,底层是 ExceptionHandlerExceptionResolver支持的
-
@ExceptionHandler值为处理的异常的类型
@ControllerAdvice public class Exception { @ExceptionHandler(ArithmeticException.class) public String toLogin(HttpSession session){ return "login"; } }
-
-
@ResponseStatus标识在自定义异常类,底层是ResponseStatusExceptionResolver支持的,底层调用response.sendError(statusCode, resolvedReason)发送responsestatus注解的信息,由tomcat发送到/error
-
@ResponseStatus属性有状态码和原因,标识在自定义异常类上
-
在需要的时候抛出自定义异常
@ResponseStatus(value = HttpStatus.FORBIDDEN,reason = "用户太多") public class UserToMany extends RuntimeException { public UserToMany() { } public UserToMany(String message) { super(message); } }
-
-
Spring底层处理异常,如参数类型转换异常,由DefaultHandlerExceptionResolver处理框架底层的异常
-
response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage())
-
-
自定义实现HandlerExceptionResolver处理异常,配置最高优先级则可以作为默认的全局异常处理规则
@Order(value = Ordered.HIGHEST_PRECEDENCE)//值越小优先级越高 @Component public class CustomerHanderException implements HandlerExceptionResolver { @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, java.lang.Exception ex) { try { response.sendError(511, "自定义错误"); } catch (java.lang.Exception e) { e.printStackTrace(); } return new ModelAndView(); } }
-
ErrorViewResolver实现自定义处理异常
-
显式调用response.sendError(...) ,error请求就会转给basicErrorController
-
异常没有任何解析器能处理,则tomcat底层调用response.sendError(..),error请求就会转给basicErrorController
-
basicErrorController 要去的页面地址是由ErrorViewResolver指定的规则决定的(根据状态码跳转)
-
异常处理自动配置原理
-
ErrorMvcAutoConfiguration 自动配置异常处理规则
-
ErrorMvcAutoConfiguration容器中的组件
-
类型:DefaultErrorAttributes -> id:errorAttributes
-
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver
-
定义错误页面中可以包含哪些数据
-
-
类型:BasicErrorController --> id:basicErrorController
-
适配响应json和白页
-
响应json数据
-
响应html错误页
-
-
默认处理/error路径的请求(可以根据server.error.path修改默认路径),页面响应 new ModelAndView("error", model)
-
-
类型:View->id:error
-
响应默认错误页【StaticView,白页】
-
-
类型: BeanNameViewResolver
-
视图解析器
-
按照返回的视图名作为组件的id去容器中找View对象(配合view组件使用)
-
-
类型:DefaultErrorViewResolver -> id:conventionErrorViewResolver
-
如果发生错误,会以HTTP的状态码作为视图页地址(viewName),找到真正的页面
-
error/404、5xx.html
-
-
异常处理流程
-
目标方法运行期间有任何异常都会被catch、而且标志当前请求结束,并且用dispatchException封装所有异常
-
进入视图解析流程 processDispatchResult(processedRequest, response, mappedHandler, mv,dispatchException)
-
目标方法正常执行mv才有值,所以此处mv值为null
-
-
mv = processHandlerException(.....)处理handler发生的异常,处理完成返回ModelAndView
-
遍历handlerExceptionResolvers【是HandlerExceptionResolver处理器异常解析器构成的list】,看谁能处理当前异常
-
系统默认的异常解析器;
-
DefaultErrorAttributes先来处理异常,把异常信息保存到request域,并且返回null
-
默认没有任何解析器能处理异常,所以异常会被抛出
-
如果没有处理,最终底层会发送 /error请求,被底层的BasicErrorController处理
-
解析错误视图,遍历所有的ErrorViewResolver 看谁能解析
-
默认的DefaultErrorViewResolver ,作用是把响应状态码作为错误页的地址凭借到默认异常路径error,error/500.html
-
模板引擎最终响应这个页面 error/500.html
-
-
web原生组件(Servlet、Filter、Listener)注入
使用servletapi
-
@ServletComponentScan(basePackages =.....) 配置类中指定原生Servlet组件的位置
-
@WebServlet(urlPatterns = "/my")标识在原生servlet,用于路径匹配,不会被spring中的拦截器拦截,原因见拓展
-
@WebFilter(urlPatterns={"拦截路径"})标识在原生Filter
-
@WebListener标识在原生Listener
-
扩展
-
DispatchServlet如何注册进来
-
容器中自动配置了DispatcherServlet ,属性绑定到 WebMvcProperties,对应的配置文件配置项是spring.mvc
-
通过 ServletRegistrationBean<DispatcherServlet> 把 DispatcherServlet配置进来
-
默认映射的是 / 路径
-
-
Tomcat中多个Servlet都能处理到同一层路径,采取精确优选原则
-
使用RegistrationBean
-
原生web组件分别对应
ServletRegistrationBean
,FilterRegistrationBean
,ServletListenerRegistrationBean
-
@Configuration的值不用设置,这样就可以实现单实例,防止冗余
@Configuration public class MyRegistConfig { @Bean public ServletRegistrationBean myServlet(){ MyServlet myServlet = new MyServlet(); return new ServletRegistrationBean(myServlet,"/my","/my02"); } @Bean public FilterRegistrationBean myFilter(){ MyFilter myFilter = new MyFilter(); //return new FilterRegistrationBean(myFilter,myServlet());拦截myServlet匹配的所有路径 FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter); filterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/css/*")); return filterRegistrationBean; } @Bean public ServletListenerRegistrationBean myListener(){ MySwervletContextListener mySwervletContextListener = new MySwervletContextListener(); return new ServletListenerRegistrationBean(mySwervletContextListener); } }
嵌入式web容器
切换和原理
-
内嵌服务器,就是手动把启动服务器的代码调用(使用tomcat服务器前提是tomcat核心jar包存在)
-
切换服务器
-
先移除默认的tomcat服务器
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency>
-
然后导入对应的服务器
-
-
原理
-
SpringBoot应用启动发现当前是Web应用(web场景包中导入tomcat)
-
web应用会创建一个web版的ioc容器 ServletWebServerApplicationContext`
-
该容器启动的时候寻找 ServletWebServerFactory(Servlet的web服务器工厂)
-
SpringBoot底层提供的WebServer工厂:TomcatServletWebServerFactory、JettyServletWebServerFactory、UndertowServletWebServerFactory..
-
-
底层有自动配置类:ServletWebServerFactoryAutoConfiguration,其中导入
-
ServletWebServerFactoryConfiguration配置类,根据动态判断系统中导入的Web服务器的包(默认web-starter导入tomcat包),创建对应的容器web服务器工厂(即默认创建TomcatServletWebServerFactory)
-
-
TomcatServletWebServerFactory创建出Tomcat服务器并启动,TomcatWebServer的构造器拥有初始化方法initialize()调用this.tomcat.start();
-
切换嵌入式Servlet容器
-
实现 WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>
-
把配置文件的值和ServletWebServerFactory进行绑定
-
xxxxxCustomizer:定制化器,可以改变xxxx的默认规则
-
-
修改配置文件server.xxx
-
直接自定义ConfigurableServletWebServerFactory
定制化原理
-
定制化的常见方式
-
修改配置文件
-
xxxxxCustomizer;
-
编写自定义的配置类 xxxConfiguration;+ @Bean替换、增加容器中默认组件
-
Web应用编写配置类实现WebMvcConfigurer+@Bean即可定制化web功能并且给容器扩展组件
@Configuration public class AdminWebConfig implements WebMvcConfigurer
-
@EnableWebMvc + WebMvcConfigurer+@Bean可以全面接管SpringMVC,所有规则需要重新配置
-
原理
-
WebMvcAutoConfiguration是默认的SpringMVC的自动配置功能类
-
一旦使用@EnableWebMvc ,会@Import(DelegatingWebMvcConfiguration.class)
-
DelegatingWebMvcConfiguration只保证SpringMVC最基本的使用
-
-
把系统中的所有WebMvcConfigurer拿过来(所有功能的定制都是这些WebMvcConfigurer合起来一起生效)
-
自动配置了一些底层的组件:RequestMappingHandlerMapping等并且组件所依赖的组件都是从容器中获取
-
-
DelegatingWebMvcConfiguration继承了WebMvcConfigurationSupport
-
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport
-
-
WebMvcAutoConfiguration里面的配置要能生效要求容器中没有WebMvcConfigurationSupport
-
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
-
-
@EnableWebMvc最终会导入WebMvcConfigurationSupport,从而导致了WebMvcAutoConfiguratio失效
-
-
-
-
定制化套路
-
场景starter - xxxxAutoConfiguration - 导入xxx组件 - 绑定xxxProperties -- 绑定配置文件项
-
数据访问
SQL
导入依赖
-
导入jdbc场景
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jdbc</artifactId> </dependency>
-
导入相应数据库驱动
-
jdbc场景中没有导入数据驱动,是因为官方不知道我们要操作的数据库
-
数据库版本要和驱动版本对应
默认版本:<mysql.version>8.0.22</mysql.version>
,想要修改版本-
依赖引入具体版本(maven的就近依赖原则)
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.49</version> </dependency>
-
重新声明版本(maven的属性就近优先原则)
<properties> <java.version>1.8</java.version> <mysql.version>5.1.49</mysql.version> </properties> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>
-
-
自动配置
-
DataSourceAutoConfiguration:数据源的自动配置
-
修改数据源相关的配置:spring.datasource,没有配置或者未正确配置都会导致项目无法启动
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC username: root password: 1212go12
-
默认的数据源是容器中没有自定义的DataSource才自动配置的
-
底层配置好的连接池是HikariDataSource
-
@Configuration(proxyBeanMethods = false) @Conditional(PooledDataSourceCondition.class) @ConditionalOnMissingBean({ DataSource.class, XADataSource.class }) @Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class, DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class, DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class }) protected static class PooledDataSourceConfiguration
-
-
DataSourceTransactionManagerAutoConfiguration: 事务管理器的自动配置
-
JdbcTemplateAutoConfiguration: JdbcTemplate的自动配置,可以来对数据库进行crud(了解就行)
-
可以修改这个配置项@ConfigurationProperties(prefix = "spring.jdbc") 来修改JdbcTemplate
-
容器中有 JdbcTemplate这个组件,可以通过自动装配注入
@Slf4j @SpringBootTest class Boot05WebAdminApplicationTests { @Autowired JdbcTemplate jdbcTemplate; @Test void contextLoads() { Long aLong = jdbcTemplate.queryForObject("select count(*) from account_tbl", Long.class); log.info("记录总数:{}",aLong); } }
-
JndiDataSourceAutoConfiguration: jndi的自动配置
-
-
XADataSourceAutoConfiguration: 分布式事务相关的
使用Druid数据源
自定义的方式
-
导入依赖
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.17</version> </dependency>
-
自定义数据源配置类
-
由于默认的数据源是判断容器中没有自定义数据源才会配置【@ConditionalOnMissingBean(DataSource.class) 】,所以配置自定义数据源会使默认数据源失效
-
使用@ConfigurationProperties注解与配置文件的属性绑定
-
@Configuration public class MyDataSourceConfig { @ConfigurationProperties("spring.datasource") @Bean public DataSource dataSource() throws SQLException { DruidDataSource druidDataSource = new DruidDataSource(); return druidDataSource; } }
-
配置druid的监控页功能
-
要在数据源配置中先开启监控功能和防火墙功能
-
druidDataSource.setFilters("stat,wall");
-
在配置文件中配置filter: stat,wall
-
-
@Bean public ServletRegistrationBean statViewServlet(){ StatViewServlet statViewServlet = new StatViewServlet(); ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(statViewServlet, "/druid/*"); registrationBean.addInitParameter("loginUsername","admin");//监控页的访问账号和密码 registrationBean.addInitParameter("loginPassword","123456"); return registrationBean; }
-
配置WebStatFilter,用于采集web-jdbc关联监控的数据
@Bean public FilterRegistrationBean webStatFilter(){ WebStatFilter webStatFilter = new WebStatFilter(); FilterRegistrationBean<WebStatFilter> filterRegistrationBean = new FilterRegistrationBean<>(webStatFilter); filterRegistrationBean.setUrlPatterns(Arrays.asList("/*")); filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"); //不拦截的内容 return filterRegistrationBean; }
引入场景依赖的方式
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.17</version> </dependency>
-
自动配置
-
扩展配置项 spring.datasource.druid
-
组件
-
DruidSpringAopConfiguration.class, 监控SpringBean【组件】,配置项:spring.datasource.druid.aop-patterns
-
DruidStatViewServletConfiguration.class, 监控页的配置:spring.datasource.druid.stat-view-servlet(默认开启)
-
DruidWebStatFilterConfiguration.class, web监控配置:spring.datasource.druid.web-stat-filter(默认开启)
-
DruidFilterConfiguration.class,Druid的所有filter的配置
private static final String FILTER_STAT_PREFIX = "spring.datasource.druid.filter.stat"; private static final String FILTER_CONFIG_PREFIX = "spring.datasource.druid.filter.config"; private static final String FILTER_ENCODING_PREFIX = "spring.datasource.druid.filter.encoding"; private static final String FILTER_SLF4J_PREFIX = "spring.datasource.druid.filter.slf4j"; private static final String FILTER_LOG4J_PREFIX = "spring.datasource.druid.filter.log4j"; private static final String FILTER_LOG4J2_PREFIX = "spring.datasource.druid.filter.log4j2"; private static final String FILTER_COMMONS_LOG_PREFIX = "spring.datasource.druid.filter.commons-log"; private static final String FILTER_WALL_PREFIX = "spring.datasource.druid.filter.wall";
-
-
-
配置示例
spring: datasource: url: jdbc:mysql://localhost:3306/db_account username: root password: 1212go12 driver-class-name: com.mysql.cj.jdbc.Driver druid: aop-patterns: com.atguigu.admin.* #监控SpringBean filters: stat,wall # 底层开启功能,stat(sql监控),wall(防火墙) stat-view-servlet: # 配置监控页功能 enabled: true login-username: admin login-password: admin resetEnable: false web-stat-filter: # 监控web enabled: true urlPattern: /* exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*' filter: stat: # 对上面filters里面的stat的详细配置 slow-sql-millis: 1000 logSlowSql: true enabled: true wall: enabled: true config: drop-table-allow: false # 不允许删除表操作
!!!整合mybatis
-
导入场景依赖
-
SpringBoot官方的Starter:spring-boot-starter-*
-
第三方的Starter: *-spring-boot-starter
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.4</version> </dependency>
-
-
mybatis配置类
@EnableConfigurationProperties(MybatisProperties.class) : @AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class }) public class MybatisAutoConfiguration{}
-
SqlSessionFactory: 自动配置好了
-
SqlSession:自动配置SqlSessionTemplate,组合了SqlSession
-
Mapper:mapper接口标识了@Mapper就会被自动扫描
-
-
配置mybatis规则
-
MyBatis配置项绑定类
@ConfigurationProperties(prefix = "mybatis") public class MybatisProperties
-
扫描全局配置文件和mapper.xml文件
mybatis: config-location: classpath:mybatis/mybatis-config.xml #全局配置文件位置 mapper-locations: classpath:mybatis/mapper/*.xml #sql映射文件位置
-
配置mybatis.configuration,就可以修改mybatis全局配置文件中的值
-
导入全局配置文件和使用配置项不能同时存在
-
mybatis: # config-location: classpath:mybatis/mybatis-config.xml mapper-locations: classpath:mybatis/mapper/*.xml configuration: # 指定mybatis全局配置相关项 map-underscore-to-camel-case: true # 开启下划线转驼峰
-
-
使用步骤
-
导入mybatis官方starter
-
编写mapper接口,标识@Mapper注解
-
在配置类上使用@MapperScan("com.atguigu.admin.mapper") 扫描mapper接口所在包,包中的接口就可以不用标注@Mapper注解
-
-
编写sql映射文件并绑定mapper接口
-
在application.yaml中指定Mapper配置文件的位置,以及指定全局配置文件信息 (建议:使用mybatis.configuration配置项)
-
整合mybatis-plus
基础操作
-
背景
-
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
-
-
导入场景依赖
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.1</version> </dependency>
-
自动配置
-
配置类:MybatisPlusAutoConfiguration,配置项绑定类:MybatisPlusProperties,前缀:mybatis-plus
-
SqlSessionFactory 自动配置好,底层是容器中默认的数据源
-
-
mapperLocations自动配置好,默认值:
classpath*:/mapper/**/*.xml
(任意包的类路径下的mapper文件夹下任意路径的所有xml) -
容器中自动配置好SqlSessionTemplate
-
@Mapper标注的接口会被自动扫描,建议直接 @MapperScan("mapper接口所在包") 批量扫描就行
-
-
优点:Mapper接口只需要继承BaseMapper<Pojo类> 就可以实现基本的crud能力
crud实战
数据访问
-
pojo
-
mybatis-plus要求映射的属性必须都在表中有对应(有点鸡肋)
-
使用@TableField标识属性并设置值为false,即声明该属性在表中不存在
-
-
类名和表名一致就可以匹配
-
类上标识@TableName,值为数据库中实际的表名,也可以进行匹配
-
-
-
service接口继承IService<pojo类>
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService
-
service的实现类继承ServiceImpl<Mapper接口,pojo类>
public interface UserService extends IService<User>
分页功能
-
配置分页插件
@Bean public MybatisPlusInterceptor paginationInnerInterceptor() { MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor(); PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();//分页拦截器 mybatisPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor); return mybatisPlusInterceptor; }
-
控制器方法
-
使用page对象获取分页信息
-
接收浏览器发送的当前页码(此参数要设置为非必须,如果没有当前页码信息就默认为第一页)
-
@RequestMapping("/dynamic_table") public String toDT(Model model,@RequestParam(value = "pn",required = false) Integer pn) { List<User> users = userService.list(); model.addAttribute("users",users); if(pn==null){ pn=1; } Page<User> userPage = new Page<>(pn, 2);//pn为浏览器发送过来的当前页码,size为每页的条数 IPage<User> page = userService.page(userPage); model.addAttribute("page",page); return "table/dynamic_table"; }
-
前端代码
-
遍历分页数据:page.records封装分页数据
<tr class="gradeX" th:each="user,stat: ${page.records}"> <td th:text="${stat.index}"></td> <td th:text="${user.username}"></td> <td th:text="${user.age}"></td> <td th:text="${user.email}"></td> <td></td> </tr>
-
分页导航栏
<ul> <li class="prev disabled"><a href="#">← 前一页</a></li> <li th:class="${num == page.current?'active':''}" #三目运算判断是否为当前页,是就渲染 th:each="num:${#numbers.sequence(1,page.pages)}" > #工具类提供的方法,作用是生成一串连续整数 <a th:href="@{/table/dynamic_table(pn=${num})}">[[${num}]]</a> </li> <li class="next disabled"><a href="#">下一页 → </a></li> </ul>
-
删除功能
-
前端代码
-
{路径变量}(请求参数1,参数2....)
-
<td> <a th:href="@{/user/delete/{id}(id=${user.id},pn=${page.current})}" class="btn btn-danger btn-sm" type="button">删除</a> </td>
-
控制器方法
-
使用重定向,接收页面发送过来的当前页码,以保证删除之后还在同一页
-
接收id用于删除单行记录
-
@Controller @RequestMapping("/user") public class UserController { @Autowired UserServiceImpl userService; @GetMapping("delete/{id}") public String delete(@PathVariable Integer id, @RequestParam(value = "pn",defaultValue = "1") Integer page ,RedirectAttributes redirectAttributes){ userService.removeById(id); redirectAttributes.addAttribute("pn",page); return "redirect:/table/dynamic_table"; } }
NoSql
Redis自动配置
-
简介
-
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)
-
-
导入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
-
自动配置
-
自动配置类:RedisAutoConfiguration,绑定属性类:RedisProperties -->前缀: spring.redis
-
连接工厂是准备好的:LettuceConnectionConfiguration、JedisConnectionConfiguration
-
自动注入RedisTemplate<Object, Object> ,用于操作Redis
-
自动注入StringRedisTemplate(k:v都是String)
-
底层只要我们使用StringRedisTemplate、RedisTemplate就可以操作redis
-
-
redis环境搭建
-
阿里云按量付费redis。经典网络
-
申请redis的公网连接地址
-
修改白名单 ,允许0.0.0.0/0 访问
-
使用完一定要释放(不然钱没了)
-
切换至jedis
-
导入jedis
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency>
-
配置文件声明使用jedis
spring: redis: host: r-bp1nc7reqesxisgxpipd.redis.rds.aliyuncs.com port: 6379 password: lfy:Lfy123456 client-type: jedis jedis:
流量统计功能
-
拦截器
@Component public class RedisUrlCountInterceptor implements HandlerInterceptor { @Autowired StringRedisTemplate redisTemplate; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String uri = request.getRequestURI(); //默认每次访问当前uri就会计数+1 redisTemplate.opsForValue().increment(uri); return true; } }
-
在拦截器配置类中,自动装配流量统计的拦截器,将其加入到其进行路径配置
@Autowired RedisUrlCountInterceptor redisUrlCountInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns("/**") //所有请求都被拦截包括静态资源 .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**", "/js/**","/aa/**"); //放行的请求 //添加流量统计拦截器 registry.addInterceptor(redisUrlCountInterceptor) .addPathPatterns("/**") .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**", "/js/**","/aa/**"); }
-
控制器方法
-
自动装配RedisTemplate用以操作redis
-
@GetMapping("/main.html") public String mainPage(HttpSession session,Model model){ ValueOperations<String, String> opsForValue = redisTemplate.opsForValue(); String s = opsForValue.get("/main.html"); String s1 = opsForValue.get("/sql"); model.addAttribute("mainCount",s); model.addAttribute("sqlCount",s1); return "main"; }
面试题
-
Filter、Interceptor 几平拥有相同的功能?
-
Filter是servlet定义的原生组件,好处是脱离Spring应用也能使用
-
Interceptor是spring定义的接口,只能在spring中使用,但是可以使用spring的自动装配功能
-
单元测试
JUnit5
相关背景
-
SpringBoot2.2.0版本开始引入 JUnit5作为单元测试默认库
-
作为最新版本的JUnit框架,JUnit5与之前版本的Junit框架有很大的不同,由三个不同子项目的几个不同模块组成
-
JUnit Platform: 是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入
-
JUnit Jupiter:提供了JUnit5的新的编程模型,是JUnit5新特性的核心,内部包含了一个测试引擎,用于在Junit Platform上运行
-
JUnit Vintage: 由于JUint已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4.x,Junit3.x的测试引擎
-
SpringBoot 2.4以上版本移除了默认对 Vintage 的依赖,如果需要兼容junit4需要自行引入(不能使用junit4的功能@Test)
-
如果需要继续兼容junit4需要自行引入vintage
<dependency> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.hamcrest</groupId> <artifactId>hamcrest-core</artifactId> </exclusion> </exclusions> </dependency>
-
-
-
SpringBoot整合Junit以后
-
编写测试方法:使用@Test标注(注意需要使用junit5版本的注解)
-
Junit类具有Spring的功能(前提是测试类要标识@SpringBootTest),例如
-
@Autowired进行装配
-
@Transactional标注测试方法,测试完成后自动回滚
-
-
常用注解
-
JUnit5的注解与JUnit4的注解有所变化,参照文档JUnit 5 User Guide
-
@Test :表示方法是测试方法。与JUnit4的@Test不同,他的职责单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
-
@ParameterizedTest :表示方法是参数化测试
-
@RepeatedTest :表示方法可重复执行,值为重复测试的次数
-
@DisplayName :为测试类或者测试方法设置展示名称
==》
-
@BeforeEach :表示在每个单元测试之前执行
-
@AfterEach :表示在每个单元测试之后执行
-
@BeforeAll :表示在所有单元测试之前执行,必须标识在静态方法上
-
@AfterAll :表示在所有单元测试之后执行,必须标识在静态方法上
-
@Tag :表示单元测试类别,类似于JUnit4中的@Categories
-
@Disabled :表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
-
@Timeout :表示测试方法运行超过了指定时间将会返回错误,单位秒
-
可以指定unit属性来设置单位
-
-
@ExtendWith :为测试类或测试方法提供扩展类引用
断言
定义
-
断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证,即先断定结果,如果运行结果一致才为合法
-
优点
-
检查业务逻辑返回的数据是否合理
-
所有测试运行结束以后,会有一个详细的测试报告
-
-
单元测试方法内部出现断言失败,后面代码不会执行
-
断言方法都是org.junit.jupiter.api.Assertions的静态方法,可以通过Asserttions.方法() 调用
JUnit5内置的断言类别
简单断言
-
用来对单个值进行简单的验证
方法 | 说明 |
---|---|
assertEquals | 判断两个对象或两个原始类型是否相等 |
assertNotEquals | 判断两个对象或两个原始类型是否不相等 |
assertSame | 判断两个对象引用是否指向同一个对象 |
assertNotSame | 判断两个对象引用是否指向不同的对象 |
assertTrue | 判断给定的布尔值是否为 true |
assertFalse | 判断给定的布尔值是否为 false |
assertNull | 判断给定的对象引用是否为 null |
assertNotNull | 判断给定的对象引用是否不为 null |
数组断言
-
通过assertArrayEquals方法来判断两个对象或原始类型的数组是否相等
@Test @DisplayName("array assertion") public void array() { assertArrayEquals(new int[]{1, 2}, new int[] {1, 2}); 相等 assertArrayEquals(new int[]{2,1}, new int[] {1, 2}); 不相等 }
组合断言
-
组合中的全部断言都成功才为成功
-
assertAll方法接受多个函数式接口的实例作为要验证的断言,可以通过 lambda 表达式提供这些断言
@Test public void all() { assertAll("组合名", () -> assertEquals(2, 1 + 1), () -> assertTrue(1 > 0) ); }
异常断言
-
JUnit5提供了一种新的断言方式Assertions.assertThrows() ,可以配合函数式编程进行使用
-
抛出的异常和断言的一致才会成功,即我赌你会抛这个异常
@Test public void exceptionTest() { ArithmeticException exception = Assertions.assertThrows( //扔出断言异常 ArithmeticException.class, () -> System.out.println(1 % 0)); }
超时断言
-
Junit5提供Assertions.assertTimeout() 为测试方法设置了超时时间
@Test @DisplayName("超时测试") public void timeoutTest() { //如果测试方法时间超过1s将会失败 Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500)); }
快速失败
-
通过fail方法直接使得当前测试失败
@Test public void shouldFail() { fail("This should fail"); }
前置条件
-
JUnit 5中的前置条件(assumptions【假设】)类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止,不会使方法失败
-
前置条件可以看成是测试方法执行的前提(当前提不满足时就没有继续执行的必要)
@DisplayName("前置条件") public class AssumptionsTest { private final String environment = "DEV"; @Test @DisplayName("simple") public void simpleAssume() { assumeTrue(Objects.equals(this.environment, "DEV")); assumeFalse(() -> Objects.equals(this.environment, "PROD")); } @Test @DisplayName("assume then do") public void assumeThenDo() { assumingThat( Objects.equals(this.environment, "DEV"), () -> System.out.println("In DEV") ); } }
-
assumeTrue和assumFalse 确保给定的条件为true或false,不满足条件会使得测试执行终止,即后续代码不执行
-
assumingThat的参数是表示条件的布尔值和对应的Executable接口的实现对象
-
只有条件满足时,Executable对象才会被执行
-
当条件不满足时,测试执行并不会终止
-
嵌套测试
-
JUnit 5可以通过 Java 中的内部类和@Nested注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起
-
在内部类中可以使用@BeforeEach和@AfterEach 注解,而且嵌套层次没有限制
-
@Nested标识在内部类上,表示整个类是嵌套测试
-
外层的测试方法不能驱动内层的@BeforeEach/@AfterEach等方法,但是内层的test可以驱动外层的
@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()); } } } }
!!!参数化测试
-
参数化测试是JUnit5很重要的一个新特性
-
利用@ValueSource等注解,指定入参,可以使用不同的参数进行多次单元测试
-
不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码
-
支持外部的各类入参(CSV,YML,JSON文件甚至方法的返回值)也可以,只需要去实现ArgumentsProvider接口,任何外部文件都可以作为它的入参
-
-
相关注解
-
@ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
-
@NullSource: 表示为参数化测试提供一个null的入参
-
@EnumSource: 表示为参数化测试提供一个枚举入参
-
@CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
-
@MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)
-
@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"); }
迁移指南
-
在进行迁移的时候需要注意如下的变化:
-
注解在 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场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能
-
不同版本的不同
使用
-
引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
-
访问 http://localhost:8080/actuator/端点(Endpoint)
-
测试
http://localhost:8080/actuator/beans
http://localhost:8080/actuator/configprops
http://localhost:8080/actuator/metrics
http://localhost:8080/actuator/metrics/jvm.gc.pause (可以访问端点的具体指标)
Actuator Endpoint
管理Endpoints
-
management是所有actuator的配置前缀,其中endpoints属性用于配置端点
-
默认除shutdown之外的所有端点都开启
-
开启或者禁用某个Endpoint的配置 management.endpoint.endpointName.enabled = true
-
对某个端点进行配置 :management.endpoint.具体端点.xxx
-
management: endpoint: beans: enabled: true
-
配置开启所有监控端点(默认开启)
management: endpoints: enabled-by-default: true #暴露所有端点信息
-
-
spring底层有两种监控模式
-
JMX:默认暴露所有Endpoint
-
HTTP:默认只暴露health和infoEndpoint
-
配置以http方式暴露所有端点
-
management: endpoints: enabled-by-default: true #暴露所有端点信息 web: exposure: include: '*' #以web方式暴露
-
除过health和info,剩下的Endpoint都应该进行保护访问,如果引入SpringSecurity,则会默认配置安全访问规则
-
常用端点
-
最常用的Endpoint
-
Health:监控状况
-
Metrics:运行时指标
-
Loggers:日志记录
-
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 。 |
Health Endpoint
-
健康检查端点,一般用于在云平台(平台会定时的检查应用的健康状况),可以为平台返回当前应用的一系列组件健康状况的集合
-
重要的几点:
-
health endpoint返回的结果是一系列健康检查后的一个汇总报告
-
所有组件全健康才为健康
-
-
很多的健康检查默认已经自动配置好了,比如:数据库、redis等
-
可以很容易的添加自定义的健康检查机制
-
-
配置健康端点显示详细信息(默认不启用)
management: endpoint: health: show-details: always
Metrics Endpoint
-
提供详细的、层级的、空间指标信息,这些信息可以通过pull(主动推送)或者push(被动获取)方式得到
-
通过Metrics对接多种监控系统
-
简化核心Metrics开发
-
添加自定义Metrics或者扩展已有Metrics
-
-
可以查看每一级具体的信息http://localhost:8080/actuator/metrics/jvm.gc.pause
定制Endpoint
定制 Health 信息
-
实现HealthIndicator接口,重写检查方法
@Component public class MyHealthIndicator implements HealthIndicator { @Override public Health health() { int errorCode = check(); // perform some specific health check if (errorCode != 0) { return Health.down().withDetail("Error Code", errorCode).build(); } return Health.up().build(); } } 构建Health Health build = Health.down() .withDetail("msg", "error service") .withDetail("code", "500") .withException(new RuntimeException()) .build();
-
也可以继承AbstractHealthIndicator类
@Component public class MyComHealthIndicator extends AbstractHealthIndicator { //真实的检查方法 @Override protected void doHealthCheck(Health.Builder builder) throws Exception { //mongodb。 获取连接进行测试 Map<String,Object> map = new HashMap<>(); // 检查完成 if(1 == 2){ //builder.up(); //健康 builder.status(Status.UP); }else { //builder.down(); builder.status(Status.OUT_OF_SERVICE); } builder.withDetail("code",100) .withDetails(map); //保存详细的健康详细信息 } }
定制info信息
-
有两种方式
-
编写配置文件
info: appName: boot-admin version: 2.0.1 mavenProjectName: @project.artifactId@ #使用@@可以获取maven的pom文件值 mavenProjectVersion: @project.version@
-
编写InfoContributor
-
使用withDetail编写key-value信息
-
@Component public class ExampleInfoContributor implements InfoContributor { @Override public void contribute(Info.Builder builder) { builder.withDetail("msg","你好") .withDetail("hello","world") .withDetail(Collections.singletonMap("key", "value")); } }
-
定制Metrics信息
-
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
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); }
定制Endpoint
@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...."); } }
-
@ReadOperation标识读方法,即访问相应端点会展示的内容
-
@WriteOperation标识操作方法
-
场景:开发ReadinessEndpoint来管理程序是否就绪,或者LivenessEndpoint来管理程序是否存活
定制可视化界面
-
创建一个新应用专门用于监控
-
只需要选中web开发场景
-
引入应用场景
<dependency> <groupId>de.codecentric</groupId> <artifactId>spring-boot-admin-starter-server</artifactId> <version>2.3.1</version> </dependency>
-
在主配置类上使用@EnableAdminServer,开启admin监控功能
-
修改默认端口号(假设改为8888)
-
-
需要监控的项目
-
引入监控客户端
<dependency> <groupId>de.codecentric</groupId> <artifactId>spring-boot-admin-starter-client</artifactId> <version>2.3.1</version> </dependency>
-
配置监控信息
boot: admin: client: url: http://localhost:8888 #监控客户端 instance: prefer-ip: true #使用ip注册进来 application: name: boot-05-web-admin
-
原理解析
Profile功能
-
为了方便多环境适配,springboot简化了profile功能
-
application-profile功能
-
默认配置文件 application.yaml/properties(不带标识的),任何时候都会加载
-
指定环境配置文件 application-指定环境.yaml
-
-
激活指定环境
-
在默认配置文件中激活
-
spring.profiles.active=指定环境
-
-
-
命令行激活:java -jar 项目.jar --spring.profiles.active=prod --person.name=haha
-
修改配置文件的任意值,命令行优先
-
可以在部署jar包时修改环境
-
-
默认配置与环境配置同时生效
-
同名配置项,profile配置优先(精确优先)
-
@Profile条件装配功能
-
@Profile("具体环境")
-
即所标识的类/方法在什么环境下生效
@Configuration(proxyBeanMethods = false) @Profile("production") public class ProductionConfiguration { }
-
-
profile分组
-
配置spring.profiles.group.组名[0]=成员1,spring.profiles.group.组名[1]=成员2.....
-
激活一个组,组内的所有环境都会生效
spring.profiles.group.production[0]=proddb spring.profiles.group.production[1]=prodmq spring.profiles.active=production 激活
-
外部化配置
基本信息
-
即把配置信息放在外部统一管理,配置信息属性值可以使用注释直接注入到您的 bean
-
外部配置源,常用
-
Java属性文件(properties)
-
YAML文件
-
环境变量
-
可以在主程序类获取系统环境变量和属性相关信息
ConfigurableApplicationContext run = SpringApplication.run(WebApplication.class, args); ConfigurableEnvironment environment = run.getEnvironment(); Map<String, Object> systemEnvironment = environment.getSystemEnvironment();//获取系统环境变量 Map<String, Object> systemProperties = environment.getSystemProperties();//获取系统属性 System.out.println(systemEnvironment); System.out.println(systemProperties);
-
使用环境变量/系统属性给属性赋值
@Value("${MAVEN_HOME}") String msg; @Value("${os.name}") String os; @RequestMapping("/msg") @ResponseBody public String msg(){ return msg+"\n"+os; }
-
-
命令行参数
-
重写规则
-
Spring Boot使用一个特别的配置源顺序,旨在允许合理地重写值。 后面的配置源可以覆盖前面配置源源中定义的值。 按以下顺序考虑。
-
默认属性(通过
SpringApplication.setDefaultProperties
指定)。 -
@Configuration 类上的 @PropertySource 注解。请注意,这样的属性源直到application context被刷新时才会被添加到环境中。这对于配置某些属性来说已经太晚了,比如
logging.*
和spring.main.*
,它们在刷新开始前就已经被读取了 -
配置数据(如
application.properties
文件) -
RandomValuePropertySource
,它只有random.*
属性 -
操作系统环境变量
-
Java System properties (
System.getProperties()
) -
java:comp/env
中的 JNDI 属性 -
ServletContext
init parameters -
ServletConfig
init parameters -
来自
SPRING_APPLICATION_JSON
的属性(嵌入环境变量或系统属性中的内联JSON) -
命令行参数
-
测试中的
properties
属性。在 @SpringBootTest 和测试注解中可用,用于测试你的应用程序的一个特定片断 -
@DynamicPropertySource 注解在你的测试中
-
你测试中的TestPropertySource (Spring Framework 6.1.0-M1 API)[
@TestPropertySource
] 注解. -
当devtools处于活动状态时,
$HOME/.config/spring-boot
目录下的Devtools全局设置属性
-
配置文件查找位置
-
classpath根路径
-
classpath根路径下config目录
-
项目打包后jar包当前目录
-
jar包当前目录的config目录
-
/config子目录的直接子目录(一层子目录下,只能在linux系统使用 )
-
同名配置属性也是后面的配置源覆盖前面的配置源
配置文件加载顺序
-
当前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 -> xxxxProperties
-
-
@Configuration
-
@Conditional
-
@EnableConfigurationProperties
-
@Bean
-
引入starter --- xxxAutoConfiguration --- 容器中放入组件 ---- 绑定xxxProperties ---- 配置项
自定义starter
-
创建场景启动器
-
创建自动配置模块,所有自定义配置都放在该模块
-
场景启动器要引入自动配置模块
<dependency> <groupId>com.example</groupId> <artifactId>mystarter-autoconfiguration</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
-
在自动配置模块中
-
编写业务类HelloService,默认不放在容器中
public class HelloService { @Autowired HelloProperties properties; public String sayHello(String username){ return properties.getPrefix()+username+properties.getSuffix(); } }
-
编写配置类属性绑定类HelloProperties,设置配置项hello
@ConfigurationProperties("hello") public class HelloProperties { private String prefix; private String suffix; public HelloProperties() { } public HelloProperties(String prefix, String suffix) { this.prefix = prefix; this.suffix = suffix; } public String getPrefix() { return prefix; } public void setPrefix(String prefix) { this.prefix = prefix; } public String getSuffix() { return suffix; } public void setSuffix(String suffix) { this.suffix = suffix; } }
-
编写配置类,在容器中注入组件,并且开启属性文件绑定功能(绑定HelloProperties)
@Configuration @EnableConfigurationProperties(HelloProperties.class) @ConditionalOnMissingBean(HelloService.class)//即容器中没有相应的业务类就使用默认的业务类 public class HelloAutoConfiguration { @Bean public HelloService service(){ return new HelloService(); } }
-
创建META-INF/spring.factories文件,指定项目启动要默认启动的配置类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.example.config.HelloAutoConfiguration
-
-
使用clean,install对启动器和自动配置包进行打包
-
引用自定义场景启动器
-
引入启动器依赖
<groupId>com.example</groupId> <artifactId>mystarter</artifactId> <version>0.0.1-SNAPSHOT</version>
-
根据配置项配置属性
hello: suffix: 45 prefix: 66
-
获取组件,编写控制器方法
@Autowired HelloService service; @RequestMapping("/service") @ResponseBody public String servie(){ return service.sayHello("liao"); }
-
测试结果
-
原理解析
springboot启动过程
创建 SpringApplication
-
使用ClassUtils工具类来判定当前应用的类型(一般是servlet),然后设置项目类型
-
从spring.factories文件中的org.springframework.boot.Bootstrapper获取所有初始启动引导器Bootstrapper,然后通过(List<Bootstrapper>)bootstrappers封装
-
从spring.factories找所有ApplicationContextInitializer(应用初始化器),保存到(List<ApplicationContextInitializer<?>)initializers
-
从spring.factories找ApplicationListener(应用监听器),保存到(List<ApplicationListener<?>)listeners
-
方法都是去spring.factories找相关配置
运行 SpringApplication
-
StopWatch【监听整个应用程序启动停止的监听器】记录应用的启动时间
-
createBootstrapContext()创建引导上下文(Context环境)
-
获取到之前保存好信息的bootstrappers,挨个执行intitialize() 来设置引导启动器上下文环境
-
-
让当前应用进入headless模式(java.awt.headless,自力更生模式)
-
获取所有SpringApplicationRunListener(运行监听器)
-
调用getSpringFactoriesInstances() 从spring.factories找 SpringApplicationRunListener
-
-
调用所有SpringApplicationRunListener的starting方法
-
通知所有Listener当前项目正在启动,便与所有监听器进行感知
-
-
使用ApplicationArguments保存命令行参数
-
调用prepareEnvironment()准备环境
-
返回或者创建基础环境信息对象StandardServletEnvironment
-
配置环境信息对象
-
读取所有的配置源的配置属性值
-
-
绑定环境信息
-
监听器调用 listener.environmentPrepared()
-
通知所有的监听器当前环境准备完成
-
-
-
createApplicationContext()创建IOC容器
-
根据项目类型创建容器
-
项目类型是Servlet会创建AnnotationConfigServletWebServerApplicationContext()
-
-
prepareContext()准备ApplicationContext IOC容器的基本信息
-
保存环境信息
-
IOC容器的后置处理流程
-
应用初始化器applyInitializers
-
遍历所有的 ApplicationContextInitializer,调用 initialize()来对ioc容器进行初始化扩展功能
-
遍历所有的 listener,调用contextPrepared(),通知所有的监听器容器上下文环境准备好了
-
-
所有的监听器调用contextLoaded(),通知所有的监听器ioc容器已加载
-
-
refreshContext()刷新IOC容器
-
创建容器中的所有组件
-
容器刷新完成后调用afterRefresh()
-
listeners.started(context)所有监听器调用started(),通知所有的监听器当前项目已经启动
-
-
callRunners()调用所有runner
-
获取容器中
-
ApplicationRunner
-
CommandLineRunner
-
-
合并所有runner并且按照@Order进行排序
-
遍历所有的runner,调用 run()方法
-
-
如果以上有异常,调用Listener的failed(),通知所有的监听器当前失败
-
调用所有监听器的 running 方法 listeners.running(context); 通知所有的监听器 running
-
running如果有问题, 调用所有Listener的failed()