java学习之SpringBoot2
参考资料
尚硅谷springboot2教程
笔记地址
尚硅谷springboot笔记
SpringBoot是一个高层的框架。他的底层就是我们之前学习过的springFrameWork,也就是说Spring可以帮助我们整合spring,防止“配置地狱”,能快速创建出生产级别的Spring应用
a)SpringBoot的优点:
SpringBoot是整合Spring技术栈的一站式框架
SpringBoot是简化Spring技术栈的快速开发脚手架
● 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依赖,简化构建配置(启动器,减少需要导入的jar包数量,减少错误)
● 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
b)SpringBoot的缺点:
● 人称版本帝,迭代快,需要时刻关注变化
● 封装太深,内部原理复杂,不容易精通
c)大时代背景
微服务,分布式,云原生
详情
d)SpringBoot的官方文档
官方网页地址
点击Reference.Doc
详细原文链接
系统要求:
● Java 8 & 兼容java14 .
● Maven 3.3+
● idea 2019.1.2
maven设置:
阿里云的国内镜像以及使用JDK1.8编译
a)创建一个maven工程
b)引入依赖
c)创建主程序(入口)
d)编写业务(无需任何过多的配置)
e)测试
直接运行主程序中的main方法即可
需求:浏览发送/hello请求,响应 Hello,Spring Boot
运行后的控制台信息
这时候我们直接在浏览器访问即可
f)简化配置
在resources中创建一个application.properties文件
这是一个统一的配置文件,springboot中的所有配置都可以在这里写
配置文件中可以写哪些信息可以在官方文档中下图的位置找到
g)简化部署
我们不必在pom.xml中修改部署方式为war包了
此时,我们使用maven的package操作后,可以把项目打成jar包,直接在目标服务器执行即可。
我们创建的项目使用父项目做依赖管理,子项目继承后,就不需要写版本号了
这个父项目还有一个父项目
点进去后发现这个spring-boot-dependencies已经声明了很多jar包的版本号,所以我们就无需写版本号了
当然,我们也可以手动修改,使用properties标签,下面假设我们需要修改mysql的版本号
版本号可以在这个网站搜索
mvn
总结一下:
我们可以先在spring-boot-dependencies中查看依赖的版本号,如果我们需要修改,在当前工程中使用properties标签即可
我们在开发中,可能会用到非常多的spring-boot-starter-* : *就某种场景开头的依赖,这个意思就是引入一组完整的依赖,只要引入starter,这个场景的所有常规依赖都会被自动引入
点击官方文档的use springboot,再如下图点击,就可以查看springboot所有支持的场景
我们也可能见到 *-spring-boot-starter: 第三方为我们提供的简化开发的场景启动器。
点进每一个starter,所有的场景启动器中最底层的依赖是下面这个springboot的核心依赖
1.自动配好了Tomcat
①引入Tomcat依赖(在前面的依赖管理中引入)
②配置Tomcat
2.自动配好SpringMVC
①引入SpringMVC全套组件
②自动配好SpringMVC常用组件(功能)
3.自动配好Web常见功能,如:字符编码问题
SpringBoot帮我们配置好了所有web开发的常见场景
在主程序中可以查看
4.默认的包结构
①主程序所在的包及其下面的所有子包,里面的组件都会被默认扫描(以外的包则不会)
②所以我们无需配置包扫描
③如果要手动改变包的路径,可以在主程序中做以下修改
5.各种配置有默认值
默认配置最终都是映射到某个类上,如:MultipartProperties(文件上传),如果想要修改,直接在application.properties中修改即可
6.按需加载所有自动配置项
①非常多的starter
②引入了哪些场景这个场景的自动配置才会开启
③SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure 包里面
回忆一下原生的Spring,当我们需要添加组件时,就需要一个xml配置文件,并用id来添加一个类,再用property为其的属性赋值,但在springboot中,我们不再需要这么麻烦了,下面介绍几种方式
a)@Configuration
用Configuration可以创建一个配置类,来代替xml
重点关注:proxyBeanMethods
回顾一下原生spring中标识组件的注释
@Bean
@Component
@Controller:控制器
@Service:业务逻辑组件
@Repository:数据库组件
一个简单的配置类:
在主程序中的测试
b)@Import
在容器中导入,可以写在任何在容器中的组件上,参数是一个数组,可以在容器中自动创建出相应类型的组件,默认的组件名字就是全类名,是通过无参的构造方法创建的组件
c)@Conditional
条件装配:满足Conditional指定的条件,则进行组件注入
注意@Conditional是一个根注解,其下面还有很多派生的注解
下面举两个例子
@ConditionalOnBean:容器中有组件时
@ConditionalOnMissingBean容器中没有组件时
这个注释可以用在方法和类名上
@ImportResource
可以利用这个注释,导入原生的spring的配置文件,使其生效
a)@Component + @ConfigurationProperties
现在假设我们有一个实体类Car,我们要通过配置文件application.properties中的值,在容器中注册一个组件,这种方法的注释写在对应的实体类上
配置文件中:
注册方法:
此时我们的容器中就已经注册了一个组件
b)@EnableConfigurationProperties + @ConfigurationProperties
@EnableConfigurationProperties注释写在配置类上
当我们这么写了以后,在Car的实体类上就可以把@Component去掉了
1、主程序中的SpringBootApplication
上文也说了,这是一个组合注解,我们看源码,下面就来分析一下这个组合注解的各个成分
a)SpringBootConfiguration
与原生spring中的@Configuration类似,代表这是一个配置类
b)ComponentScan
指定要扫描那些包
c)EnableAutoConfiguration(重点)
点进源码,发现他也是一个合成注解
①@AutoConfigurationPackage
该注解的作用,是实现自动导入包
利用Registrar来批量导入一系列组件:将指定的一个包下的所有组件导入,没有自己写路径就是MainApplication(主程序)所在包下。
②@Import({AutoConfigurationImportSelector.class})
1、利用getAutoConfigurationEntry方法,在容器中批量导入一些组件
2、调用List configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类
3、利用工厂加载Map<String, List> loadSpringFactories(@Nullable ClassLoader classLoader),得到所有的组件
4、从META-INF/spring.factories位置来加载一个文件,默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
其中,核心在下图的这个包里
这个文件里面写死了springboot一启动就要给容器中加载的所有配置类,共127个
5、虽然我们127个场景的所有自动配置启动的时候默认全部加载。xxxxAutoConfiguration(某某自动配置类)
每一个xxxxAutoConfiguration类按照条件装配规则(@Conditional),最终会按需配置。
下面举一个文件上传解析器的例子
6、SpringBoot默认会在底层配好所有的组件。但是如果用户自己配置了以用户的优先
下面以用户字符编码过滤器为例,只有用户没配,springboot才会配
7、总结:
● SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration
● 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties里面拿。xxxProperties和配置文件application.properties进行了绑定,所以我们只需要改配置文件就可以了
● 生效的配置类就会给容器中装配很多组件
● 只要容器中有这些组件,相当于这些功能就有了
● 定制化配置
○ 用户直接自己@Bean替换底层的组件
○ 用户去看这个组件是获取的配置文件什么值就去修改,可以查看官方文档去看有什么配置文件
xxxxxAutoConfiguration —> 组件 —> xxxxProperties里面拿值 ---->application.properties
1、引入场景依赖,这里可以参考官方文档,下面是链接
starter
2、引入场景依赖后,我们可以查看他为我们自动配置了什么
①自己分析,比较繁琐
②在配置文件中输入debug=true开启自动配置报告,再运行,然后观察控制台
3、检查是否需要修改
①自己分析,查看xxxxProperties绑定了配置文件的哪些,同样较为繁琐
②参照文档来修改配置
properties
4、 自定义加入或者替换组件
@Bean、@Component等等,或者还有XXXXXCustomizer等自定义器,后面会说到
a)Lombok
简化javabean的开发,可以简化我们在写javabean时,要写构造方法,set方法的一系列流程
首先先引入依赖
然后在idea中安装插件,注:版本较新的idea中已经自带了这个插件
完成后我们以后就可以在bean类上使用注释了:
@NoArgsConstructor:无参构造器
@AllArgsConstructor:有参构造器
@Data:get和set
@ToString
@EqualsAndHashCode:重写equals和HashCode
@Slf4j:简化日志,使用方法如下图
b)dev-tools
项目或者页面修改以后:Ctrl+F9就可以生效,不必再重启
引入依赖
c)Spring Initailizr
在新建项目时可以直接选择Spring Initailizr,快速创建一个springboot应用
next后直接勾选对应的场景就可以快速创建springboot应用
这样可以自动帮我们创建目录和主程序类,并且在pom.xml中引入好了依赖
1.基本语法
● key: value;kv之间有空格(键值对)
● 大小写敏感
● 使用空格缩进表示层级关系
● 缩进不允许使用tab,只允许空格
● 缩进的空格数不重要,只要相同层级的元素左对齐即可
● '#‘表示注释
● 字符串无需加引号,如果要加,’‘与""表示字符串内容 会被 转义/不转义,例如’'会将
作为字符串输出,""会将
作为换行输出
2.创建ymal配置文件
properties的优先级高于ymal
3.数据类型与例子
尚硅谷详细笔记
经过测试,上面笔记中存在一处问题
4.加入配置处理器
我们自定义的类,在写配置的时候一般没有提示,这个时候就可以加入下面的配置处理器
springboot已经帮我们完成了大部分的配置,所以我们大多数情况下无需进行复杂的配置比如说,静态资源的自动配置,视图解析器,等等。
a)静态资源目录
官方的帮助文档中规定了静态资源文件夹的名字,只要静态资源放在这些路径下,我们只需要通过当前项目根路径/+静态资源名就可以访问到了
原理:
静态映射/**(拦截所有请求)
请求一进来,先看Controller能不能处理,若不能,就交给静态资源管理器,就会去下图的几个文件夹中找,如果还找不到,则报404错误
我们也可以设置一个静态资源的访问前缀,默认是无前缀的,我们可以在配置文件中设置
这个效果就是当前项目+路径名+静态资源名,就会去你的静态资源文件夹下找
当然,默认的静态资源存储的路径也可以修改
这样修改后,系统就会默认你的静态资源存储在resources中你的文件夹下了,当然,上文的几个静态资源的文件夹同样生效
我们也可以访问webjars
webjars
引入对应的依赖
访问地址:http://localhost:8080/webjars/jquery/3.5.1/jquery.js 后面地址要按照依赖里面的包路径
a)欢迎页
两种方式:
静态资源下的index.html(可以配置静态资源路径,但是不可以配置静态资源的访问前缀。否则导致 index.html不能被默认访问)若没有上面的html页面,则需要一个能处理/index的controller方法 b)自定义网站图标
我们只需要将我们想设置的网页图标的图片文件名改为favicon.ico ,放在静态资源目录下即可
注意:静态资源的访问前缀也会影响这个功能
a)静态资源配置原理
1、首先,SpringBoot启动默认加载 xxxAutoConfiguration 类(自动配置类),SpringMVC功能的自动配置类 WebMvcAutoConfiguration,经过查看,发现其生效
2、再查看这个自动配置类给容器中配了什么,经过寻找,发现了下面这样一个内部类
配置文件的相关属性和xxx进行了绑定。WebMvcProperties== spring.mvc、WebProperties==web.resources
3、这个配置类只有一个有参构造器
那么有参构造器中所有参数的值都会从容器中确定,一共有如下的几个参数
WebProperties webProperties;获取和spring.web绑定的所有的值的对象
WebMvcProperties mvcProperties 获取和spring.mvc绑定的所有的值的对象
ListableBeanFactory beanFactory Spring的beanFactory
HttpMessageConverters 找到所有的HttpMessageConverters
ResourceHandlerRegistrationCustomizer 找到 资源处理器的自定义器。=========
DispatcherServletPath
ServletRegistrationBean 给应用注册Servlet、Filter…
4、下面是静态资源处理的源码
1.禁用所有静态资源(add-mappings)
2.点进getStaticLocations方法找到静态资源的默认文件夹
3.源码中欢迎页的处理规则
a)请求映射(xxxMapping)
Rest风格的映射例子:
/user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
○ 核心Filter;HiddenHttpMethodFilter
■ 用法: 表单method=post,隐藏域 _method=put
■ SpringBoot中手动开启,我们可以直接运用,下面是个小例子
b)rest源码(基于表单提交)
只针对于表单form的提交,因为form不支持put和delete,若使用客户端能直接发这两个请求,就不需要开启这个功能了。
1、表单提交会带上一个_method参数,其值代表真正的请求方式
2、请求过来会被HiddenHttpMethodFilter拦截
3、处理请求
除了post和get,兼容delete与put
4、如何把_method改成我们自己喜欢的名字
自己在容器中放一个HiddenHttpMethodFilter
c)源码分析映射原理
我们来分析一下DispatcherServlet的继承关系
SpringMVC功能分析都从 org.springframework.web.servlet.DispatcherServlet-》doDispatch()开启分析
我们点进这个getHandler方法
HandlerMapping:处理器映射。/xxx->>xxxx
下面是RequestMapping的详细信息
源码的处理流程:
所有的请求映射都在HandlerMapping中。
● SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;
● SpringBoot自动配置了默认 的 RequestMappingHandlerMapping,保存用户的映射
● 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。
○ 如果有就找到这个请求对应的handler
○ 如果没有就是下一个 HandlerMapping
若我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping
这些注解与springmvc很类似,可以查看下面这篇文章的获取参数部分
springmvc笔记
a)@PathVariable
这个注解可以获取占位符风格的参数,也可以将参数直接装在一个map集合中
测试效果
b)@RequestHeader
用于获取请求头中的信息
在刚刚的方法参数中加上:
c)RequestParam
获取请求参数
d)@CookieValue
获取Cookie的值
e)@RequestBody
获取请求体信息(只有post请求才有)
这样操作,我们使用字符串获取了一个表单提交过来的username和email
f)@RequestAttribute
获取request域中的属性
g)@MatrixVariable(矩阵变量)
应用场景:在页面开发中,如果cookie被禁用了,那么session中的值怎么获取。
我们知道,当我们在session中保存了某个数据,每一个session都有一个jessionid,这个id会保存在cookie中,并且每次发请求都会携带cookie,所以发请求时,服务器就可以根据cookie中的这个id,找到session,再调用get方法,就可以找到数据。
这个时候,我们就可以通过url重写,也就是把cookie的值使用矩阵变量的方式进行传递,以分号分割,下面是一个例子
/boss/1;age=20/2;age=20
分号前面,是真正的访问路径,分号后面是矩阵变了
下面是测试,注意,springboot是默认禁用了矩阵变量,需要手动开启
①开启矩阵变量
原理。对于路径的处理。UrlPathHelper进行解析。
removeSemicolonContent(移除分号内容)支持矩阵变量的
当然还有第二种写法,直接在配置类中放一个WebMvcConfigurer组件
②测试代码
注意,矩阵变量必须有url路径变量才能被解析
h)酸爽的源码解析
①参数处理原理(DispatcherServlet的doDispatch方法开始分析)
通过前面的分析,我们知道了发送一个请求后,首先是在HandlerMapping中找到能处理请求的Handler(Controller)方法
然后继续往下走,为当前的Handler找一个适配器HandlerAdapter(一般都是RequestMappingInfoHandlerMapping),下面是源码片段
在debug模式中点击进源码中的这行代码,我们发现一共有四种HandlerAdapter
找到RequestMappingInfoHandlerMapping后,继续往后走,就到执行方法
继续往后走,在RequestMappingInfoHandlerMapping中找到了一个方法
再往下走,我们看到了下面的代码,作用是获取参数解析器,
这些参数解析器的作用就是解析我们方法中带有注解的参数
springmvc目标方法能写多少种参数类型就取决于参数解析器
点进参数解析器,他其实是一个接口
功能:
● 判断当前解析器是否支持解析这种参数
● 支持就调用 resolveArgument
继续往下走,一样的,我们有找到了返回值处理器
这就表明了我们的方法可以写多少种返回值
共有15种
springmvc将上面的参数解析器和返回值处理器都放到了目标方法的可执行方法中
继续往下走,就到了真正执行方法的地方
所以,获取方法参数的关键代码,就是getMethodArgumentValues方法
这个方法在InvocableHandlerMethod类中
目标方法执行完后,将所有数据放在ModelAndViewContainer,包含要去的页面地址View,还包含Model数据
最后就是处理结果了
②ServletAPI参数解析原理
在我们的控制器方法中,我们经常也会传入原生的servlet参数,与上面的流程类似,这些原生的参数是由ServletRequestMethodArgumentResolver这个参数解析器解析的
③Model、Map参数的解析原理
Model,Map里面的数据会被放在request请求域中,相当于调用了request.setAttribute
可以用request.getAttribute获取
这两个参数在解析时的第一步也是先找到他们对应的解析器
Map、Model类型的参数,底层都会调用并返回 mavContainer.getModel();—> BindingAwareModelMap 是Model 也是Map
④自定义参数绑定原理
数据绑定:页面提交的请求数据(Post,Get)都可以和我们的自定义对象属性进行绑定,那么接下来就来探究一下他的原理
还是一样的,处理流程的第一步也是找到一个参数解析器,经过debug,发现是ServletModelAttributeMethodProcessor这个参数解析器用于解析自定义参数
为什么会使用这个参数解析器?因为这个解析器会用下面的这些代码来判断方法参数是否为简单类型,其实就是枚举的方式
接下来在底层,会创建一个WebDataBinder:web数据绑定器,将请求参数的值绑定到指定的javaBean(由底层创建的空的java对象)中
在WebDataBinder:web中,有非常多的Converter,他就可以帮助我们Http请求中的字符,通过反射的方式,转换成指定的数据类型,最终绑定到我们的目标对象上,那么是怎么找到对应的Converter呢?其实也很简单,和我们找到参数解析器十分类似,就是遍历所有的Converter,看看哪个Converter能够实现我们需要的转换(request带来的参数的字符串转换成指定的类型)。
未来我们也可以在WebDataBinder中放自定义的Converter,下面是自定义Converter的例子
假设我们有一张表,提交一个Person对象,Person中又有一个Pet对象,Pet有name和age,那么我们常规的表单设计应该是下面的这种
但是假如说我们不适用级联属性,像下面这样提交一个字符串,这时候就要用到自定义的Converter了
下面是自定义的代码,也是在配置类中设置WebMvcConfigurer的组件
a)响应json
需要先引入jar包,当然引入web场景后这个会自动帮我们引入
使用时,我们只需要在控制器方法上加上@ResponseBody注解,我们就可以给前段返回json数据了,当然也可以直接在控制器类上写@RestController注解,这样就代表这个控制器中的每个方法都会加上@ResponseBody
接下来就来探究一下返回json数据的原理
首先,在执行之前,springboot底层会自动获取返回值解析器,这个和我们上面获取参数的流程类似
通过debug,我们在源码中发现了这个方法,寻找返回值处理器的原理也和上文寻找参数解析器很类似,都是直接通过遍历查找
下面是返回值处理器的逻辑
最后我们发现,我们带有@ResponseBody注解的,是用RequestResponseBodyMethodProcessor这个处理器来处理的
下面来拓展一下我们可以写的所有返回值的类型
ModelAndView
Model
View
ResponseEntity
ResponseBodyEmitter
StreamingResponseBody
HttpEntity
HttpHeaders
Callable
DeferredResult
ListenableFuture
CompletionStage
WebAsyncTask
有 @ModelAttribute 且为对象类型的
@ResponseBody 注解 —> RequestResponseBodyMethodProcessor;
寻找完返回值处理器后,就要处理返回值了,通过debug,我们在RequestResponseBodyMethodProcessor类中找到了最终处理返回值的方法,翻译过来就是利用MessageConverter(消息处理器)把返回值写为json
通过寻找消息转换器后(下文内容协商的内容),最终通过MappingJackson2HttpMessageConverter把对象转换为json
所以@ResponseBody注解的实质就是在底层调用返回值处理器里面的消息处理器进行处理,实现将数据转换为json给服务器
b)内容协商
1.浏览器默认会用请求头的方式告诉服务器他能接受什么样的内容类型
2.服务器最终根据自身的能力,决定服务器能生产出什么样内容类型的数据
3.springMVC会遍历所有容器底层的HttpMessageConverter(消息转化器),看谁能处理
4.最终根据客户端接受能力的不同,返回不同媒体类型的数据
HttpMessageConverter: 看是否支持将 此 Class类型的对象,转为MediaType类型的数据。
例子:Person对象转为JSON。或者 JSON转为Person
下面是系统中自带的MessageConverter,系统会遍历这些MessageConverter,判断是否可以处理,当然,我们导入对应的jar包后,对应的MessageConverter就会自动添加进来
内容协商的原理:
1、 判断当前响应头中是否已经有确定的媒体类型,如果有,就用之前确定的媒体类型,如果没有,继续往后
2、获取客户端支持的支持接受的请求类型(获取客户端Accept请求头字段)【application/xml】
3、遍历循环所有当前系统的MessageConverter,看谁支持操作我们的方法返回值对象【Person】
4、找到支持操作Person的Converter,把Converter支持的媒体类型(可以返回给客户端的)统计出来
5、客户端需要【application/xml】,当前服务端的能力【10种,json,xml】
6、下面进入最关键的匹配阶段,其实就是一个双重循环(最佳匹配),对于上面的那个例子,客户端需要【application/xml】,进行最佳匹配后,我们发现4号和7号MessageConverter可以处理
7、按照权重给上面找到的MessageConverter进行排序,用优先级最高的进行数据的处理
c)响应xml
首先还是引入xml依赖
d)基于请求参数的内容协商
上文的内容协商是基于请求头中的accept参数的,为了方便内容协商,我们也可以使用基于请求参数的内容协商
首先 在yaml配置文件中设置这个参数为true
使用时我们只需要在请求后加上一个format参数
基于参数的内容协商他的优先级是高于基于请求头的
总结一下前面的响应数据的原理
0、@ResponseBody 响应数据出去 调用 RequestResponseBodyMethodProcessor 处理
1、Processor 处理方法返回值。通过 MessageConverter 处理
2、所有 MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写)
3、内容协商找到最终的 messageConverter;
e)自定义MessageConverter
背景:
当app发请求,他需要服务器返回application/x-guigu这种类型的媒体数据,我们就需要我们自定义的MessageConverter来进行数据的转换
最简单的方法一就是写三个方法,三种请求各对应自己的方法,但有了内容协商后,我们就可以使用一个方法来实现这个功能
步骤:
1、添加自定义的messageConverter进系统底层
想要改SpringMVC的什么功能。给容器中添加一个 WebMvcConfigurer,修改即可
这个是自己创建的MessageConverter(基于请求头的内容协商),这样设置完后,若请求头中accept的值是application/x-guigu系统就会调用我们自己定义的这个MessageConverter来进行数据的转换
注意,基于参数的内容协商默认只支持json和xml,如下图所示
所以我们需要在WebMvcConfigurer中配置内容协商功能
重写后,我们的format就可以识别三个参数了,然后底部运行时,后面的流程就和基于请求头内容协商一致了
但注意我们配置了内容协商以后,我们基于请求头的就失效了,因为系统里面的策略只剩一个基于参数的了,无论请求头中的accept是什么,最终都会返回json,为了保持基于请求头的正常运行,我们在配置时,要加上一个基于请求头的策略
2、运行时系统底层会统计出所有messageConverter能操作哪些类型
3、客户端协商后若符合条件,就会调用我们自定义的MessageConverter
注意,springboot默认不支持jsp,需要引入第三方模板引擎技术实现页面渲染
具体有哪些第三方的模板引擎详情见帮助文档
视图的处理方式主要有:转发,重定向和自定义视图三种
a)Thymeleaf的简单语法
Thymeleaf3.0帮助文档
详情见
尚硅谷官方笔记
b)Thymeleaf的使用
1、引入starter
2、引入后springboot就自动配置好了Thymeleaf
自动配好的策略:
①所有的Thymeleaf的配置值都在ThymeleafProperties
②配置好了SpringTemplateEngine
③配置好了配好了 ThymeleafViewResolver
④我们只需要直接开发页面即可
3、Thymeleaf的页面位置
打开源码:
4、一个简单的小案例
控制器方法
template下的success.html
c)构建一个后台管理系统
尚硅谷springboot笔记
所用到的静态资源的下载地址
视频教程:P43-P45
Thymeleaf官方文档中对公共资源引用的介绍:
d)视图解析器与视图的源码解析
1、执行目标方法的流程与上文说到的参数解析类似,接受到请求后, 先找到适配器,然后配置好参数解析器以及返回值处理器,返回值就是我们return的字符串,通过遍历返回值处理器,最终我们发现,处理字符串的返回值处理器是ViewNameMethodReturnValueHandler。将数据 放在 ModelAndViewContainer 里面。包括数据和视图地址
2、方法的参数是一个自定义类型对象(从请求参数中确定的),也会把他重新放在 ModelAndViewContainer中
3、任何目标方法执行完成以后都会返回 ModelAndView(数据(操作目标方法时,在model中放的数据)和视图地址(目标方法返回值))。
4、processDispatchResult(DispatcherServlet中的方法) 处理派发结果(页面改如何响应)
①调用render(mv(ModelAndView), request, response);进行页面渲染逻辑,根据方法的String返回值得到 View 对象【定义了页面的渲染逻辑】
a)所有的视图解析器尝试是否能根据当前返回值得到View对象,共有五个解析器
b)我测试用的方法返回值是 redirect:/main.html,通过debug发现调用了上图中的Thymeleaf解析器创建了一个RedirectView(重定向视图)
c)ContentNegotiationViewResolver 里面包含了上图所有的视图解析器,内部还是遍历上图所有视图解析器得到视图对象。
d)视图对象调用自定义的render方法进行页面渲染工作,分两步,第一步就是获取目标的url地址,然后调用原生的servlet重定向:response.sendRedirect();
5、上面是以redirect重定向为例子,下面是视图解析的一般过程,这个在学习springMVC的时候已经涉及到了
视图解析:
○ 返回值以 forward: 开始: 创建一个转发视图,new InternalResourceView(forwardUrl); --> 转发调用了原生servlet中的
request.getRequestDispatcher(path).forward(request, response);
○ 返回值以 redirect: 开始: 创建一个重定向视图,new RedirectView() --》 render就是重定向
○ 返回值是普通字符串: new ThymeleafView()—>
a)HandlerInterceptor 接口
下面是一个拦截器的例子
下面是在配置类中加入这个拦截器
b)源码分析
拦截器的三个方法执行顺序与springmvc类似,可以参考我之前笔记中的拦截器源码部分
springmvc笔记
1、根据当前请求,找到可以处理请求的handler以及所有的拦截器
2、先来顺序执行所有拦截器的preHandle方法,如果返回为true,则执行下一个拦截器的preHandle,如果返回为false,直接倒序执行所有已经执行的拦截器的afterCompletion,如果任何一个拦截器的preHandle返回false,就直接跳出
3、如果所有的拦截器preHandle都返回true,执行目标方法,倒序执行所有拦截器的postHandle方法,前面的所有步骤如果有任何异常,都会直接触发已执行拦截器的afterCompletion方法
4、页面渲染完后,倒序执行已执行拦截器的afterCompletion
5、用一张图来形象生动的描述
a)测试用表单
b)在配置文件中修改相关设置
所有根文件有关的设置的开头
c)用于接受并保存文件的控制器方法
重点:@RequestPar注解,MultipartFile ,transferTo方法
d)源码分析文件上传原理
1、首先springboot帮我们自动配置的和文件上传有关的功能,都在MultipartAutoConfiguration这个类中
①自动配置好了StandardServletMultipartResolver(文件上传解析器),需要替换的话我们只需要在容器中自己放一个MultipartResolver类就行了
原理步骤:
1、当请求发送时,先使用文件上传解析器的isMultipart方法判断是不是一个文件上传请求,依据就是我们表单提交时的enctype属性,如果是,就将请求封装成一个MultipartHttpServletRequest(文件上传请求)并返回
2、经过debug,发现使用的是下图的参数解析器来解析请求中的文件内容封装成MultipartFile
3、底层实现的功能:将request中文件信息封装为一个Map;MultiValueMap<String, MultipartFile>
详细的可以在官方文档的
a)默认规则
● 默认情况下,Spring Boot提供/error处理所有错误的映射
● 对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据
机器客户端的json:
浏览器客户端的白页:
b)自定义错误处理逻辑
● 要完全替换默认行为,可以实现 ErrorController 并注册该类型的Bean定义,或添加ErrorAttributes类型的组件以使用现有机制但替换其内容。
● (templates或者静态资源文件夹下的)error/下的4xx,5xx页面会被自动解析;
注意:若将404改为4xx,那么所有4开头的错误都会最终响应为4xx.html
c)异常处理的自动配置原理(源码)
下面是三个组件,通过分析源码中这三个组件的作用,我们就可以知道当我们要自定义错误信息时,要修改哪个错误组件了
1、找到ErrorMvcAutoConfiguration这个类,它自动配置了异常处理规则,下面是容器中的各个组件
○ 容器中的组件:类型:DefaultErrorAttributes -> id:errorAttributes
■ public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver
■ DefaultErrorAttributes:定义错误页面中可以包含哪些数据(下图只是一部分)
○ 容器中的组件:类型:BasicErrorController --> id:basicErrorController(json(客户端)+白页(浏览器))
■ 处理默认 /error 路径的请求;页面响应 new ModelAndView(“error”, model);
■ 容器中有组件 View->id是error;(响应默认错误页)
■ 容器中放组件 BeanNameViewResolver(视图解析器);按照返回的视图名作为组件的id去容器中找View对象。
所以如果想要返回页面,就会找error视图【StaticView】(默认是一个白页)
在BasicErrorController中有两个方法,一个是响应json的,一个是响应页面的
响应页面的
响应json的
○ 容器中的组件:类型:DefaultErrorViewResolver -> id:conventionErrorViewResolver
■ 如果发生错误,会以HTTP的状态码 作为视图页地址(viewName),找到真正的页面
■ error/viewName.html
d)异常处理的流程(源码)
1、执行目标方法,目标方法运行期间有任何异常,都会被catch,而且标志当前请求结束,并且用dispatchException封装
2、进入视图解析流程(准备页面跳转)
3、处理handler发生的异常,处理完成返回mv(ModelAndView)
4、第三步中处理handler发生的异常的第一步:遍历所有的handlerExceptionResolvers(异常解析器),看谁可以处理异常,系统默认的异常解析器
5、 DefaultErrorAttributes先来处理异常,他把异常信息保存到了request域,并且返回空
6、经过测试,默认没有任何异常解析器能够处理int i = 10/0的异常,所以异常会被抛出,触发拦截器的after方法
7、没有任何人能处理,最终底层就会发送一个/error请求由上文说过的BasicErrorController专门处理
8、最终,调用到了上文的DefaultErrorViewResolver 来响应异常,作用在上文也讲过,就是把响应的状态码作为错误页的地址,然后再找到error/下的html
e)定制错误处理逻辑
方式一:自定义错误页
这个在前面已经讲过,就是error/下的4xx,5xx,有精确的错误状态码页面就匹配精确,没有就找 4xx.html;如果都没有就触发白页
方式二(推荐):@ControllerAdvice+@ExceptionHandler处理全局异常
标有@ExceptionHandler注解的,都是调用了底层的ExceptionHandlerExceptionResolver 这个异常解析器
下面是一个例子,我们专门写了个类,并且有一个方法专门处理空指针和数学异常
方式三:@ResponseStatus+自定义异常
底层是 ResponseStatusExceptionResolver 这个异常解析器,把responsestatus注解的信息底层调用 response.sendError(statusCode, resolvedReason);由tomcat发送的/error,然后再到BasicErrorController,然后到error/下的页面
下面是我们自定义的一个异常类,假设在登录用户过多时,就会抛出这个异常
方式四:Spring底层的异常,如 参数类型转换异常
DefaultHandlerExceptionResolver 处理框架底层的异常。可以在前面这个异常解析器的类中,找到有哪些异常的种类
方式五:自定义实现 HandlerExceptionResolver 处理异常
方式二三四中的异常解析器,其实都是HandlerExceptionResolver的一部分,所以我们就可以自定义一个解析器实现这个接口来处理异常,可以作为全局异常处理规则
方式六:ErrorViewResolver 实现自定义处理异常
○ response.sendError 。error请求就会转给controller
○ 你的异常没有任何人能处理。tomcat底层 response.sendError。error请求就会转给controller
○ basicErrorController 要去的页面地址是 ErrorViewResolver ;
所以只要没人处理的异常基本上都能被他捕获
a)原生servlet API
首先,先写一个原生的servlet,加上@WebServlet注解
经过测试,这个原生的servlet执行并没有经过spring的拦截器
然后再主类中加上@ServletComponentScan要扫描的包
注,其他原生组件Filter,Listener都类似于servlet,需要使用@WebFilter或@WebListener加@ServletComponentScan
b)第二种注入方式:使用RegistrationBean(推荐)
写一个配置类,返回:ServletRegistrationBean, FilterRegistrationBean, and ServletListenerRegistrationBean
c)DispatcherServlet注入原理
关于DispatcherServlet的自动配置,都在DispatcherServletAutoConfiguration中
1、容器中自动配置了 DispatcherServlet 属性绑定到 WebMvcProperties;对应的配置文件配置项是 spring.mvc。
2、通过 ServletRegistrationBean 把 DispatcherServlet 配置进来。也就是第二种方式
3、默认映射的是 / 路径
之前我们已经学习过:
Tomcat-Servlet;
多个Servlet都能处理到同一层路径,精确优选原则(最长匹配原则)
A: /my/
B: /my/1
所以我们刚刚的时候,我们自己写的servlet不会经过spring的拦截器,因为我们发/my请求时,此时有两个servlet,根据最长匹配原则,底层就走了我们自己配置的servlet,所以没有经过spring的流程
1、springboot默认支持的webServer
Tomcat, Jetty, or Undertow
2、原理
○ 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包存在)
3、定制服务器服务器
切换别的服务器,我们只需要首先排除tomcat的导入,然后再导入想用的服务器即可
若想对服务器进行修改:
1、修改配置文件 server.xxx(推荐)
2、直接自定义 ConfigurableServletWebServerFactory
a)定制化的常见方式
1、 修改配置文件;
2、 xxxxxCustomizer;
3、 编写自定义的配置类 xxxConfiguration;+ @Bean替换、增加容器中默认组件;视图解析器
4、Web应用 编写一个配置类实现 WebMvcConfigurer 即可定制化web功能;+ @Bean给容器中再扩展一些组件
5、@EnableWebMvc + WebMvcConfigurer —— @Bean 可以全面接管SpringMVC,所有规则全部自己重新配置(静态资源、视图解析器、欢迎页),所有自动配置全部失效,我们就可以实现定制和扩展功能,这个注解要慎用。
b)原理分析套路
场景starter - xxxxAutoConfiguration - 导入xxx组件 - 绑定xxxProperties – 绑定配置文件项
a)导入jdbc场景
导入的内容
但是我们发现,官方并没有帮我们导入数据库,所以接下来我们要导入数据库
对应的驱动可以去这个网站搜索
mvn
springboot底层是8版本的,如果要调整版本,需要自己修改(直接依赖引入具体版本或者在properties标签中声明版本)
b)分析自动配置
1、自动配置的类:DataSourceAutoConfiguration(数据源的自动配置)
①通过分析发现,想要修改数据源相关的配置,修改配置文件中的spring.datasource就可以了
②且数据库连接池的配置,是自己的容器中没有DataSource才自动配置的
③底层配置好的连接池是:HikariDataSource
2、自动配置的类:DataSourceTransactionManagerAutoConfiguration(数据源的事务)
3、自动配置的类:JdbcTemplateAutoConfiguration: JdbcTemplate的自动配置,可以来对数据库进行crud
○ 可以修改这个配置项@ConfigurationProperties(prefix = “spring.jdbc”) 来修改JdbcTemplate
○ @Bean@Primary JdbcTemplate;容器中有这个组件
4、自动配置的类:JndiDataSourceAutoConfiguration: jndi的自动配置
5、自动配置的类:XADataSourceAutoConfiguration: 分布式事务相关的
c)修改配置项
在yaml配置文件中的
d)测试代码
a)Druid的官方网站
Druid
在这个网站中可以找到官方文档之类的,要整合第三方的技术,通过之前的学习,主要就是自定义和找starter这两种方式
b)自定义方式整合Druid
首先,先引入数据源
然后通过配置类直接添加到我们的容器中即可,如果需要配置别的功能,比如说监控,防火墙,那么就要可以参考官方文档来配置
详细笔记见尚硅谷springboot笔记
所有调用set方法给数据源设置属性的,我们都可以在配置文件中写,然后绑定配置文件
c)Druid数据源的starter整合方式
首先,引入starter
然后我们分析一下自动配置
①配置文件的扩展配置项:spring.datasource.druid
②自动导入了
DruidSpringAopConfiguration.class:监控spring组件,配置项: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的配置
导入完以后,所有的相关设置都可以在设置文件中进行,下面举一个小例子:
下面是官方文档:
Druid中文官方文档
第三方的starter,铭铭一般都是*-spring-boot-starter
下面是官方的网址
mybatis官方github
a)配置版
首先,引入依赖
然后也是一样的,查看MybatisAutoConfiguration这个自动配置类,发现修改配置文件中mybatis开头的,就可以修改mybatis的配置,下面是通过分析自动配置类得到的结果
● 全局配置文件
● SqlSessionFactory: 自动配置好了
● SqlSession:自动配置了 SqlSessionTemplate 组合了SqlSession
● Mapper: 只要我们写的操作MyBatis的接口标准了 @Mapper 就会被自动扫描进来
第一步,在yaml配置文件中指定mybatis的全局配置文件和sql映射文件位置
接下来就和我们之前学习到mybatis使用方法一致了Mapper接口—>绑定Xml,详情见下面的文章:
mybatis学习笔记
注意接口要标注Mpper注解,以前mybatis全局配置文件中的配置,都可以写在配置文件中,以mybatis.configuration开头
下面是配置文件的例子,开启驼峰,可以不写全局;配置文件,所有全局配置文件的配置都放在configuration配置项中即可
总结,配置版的步骤:
● 导入mybatis官方starter
● 编写mapper接口。标准@Mapper注解
● 编写sql映射文件并绑定mapper接口
● 在application.yaml中指定Mapper配置文件的位置,以及指定全局配置文件的信息 (建议;配置在mybatis.configuration)
b)注解配置混合版
在新建springboot项目的时候,就可以选择mybatis框架
下面是纯注解整合mybatis的方式,其实很好理解,就是不用写mapper映射文件了,但是这种方式如果设计到复杂SQL,并不推荐使用,涉及到纷杂的sql,推荐使用上面的配置版,也就是混合配置
下面举一个复杂SQL的例子
xml中的
若要使用注解:
最后总结:引入mybatis的最佳实战
● 引入mybatis-starter
● 配置application.yaml中,指定mapper-location位置即可
● 编写Mapper接口并标注@Mapper注解
● 简单方法直接注解方式
● 复杂方法编写mapper.xml进行绑定映射
● 在配置类上用@MapperScan(“mapper接口所在的包”) 简化,其他的接口就可以不用标注@Mapper注解
c)整合mybatis-plus
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
下面是官网
mybatis-plus官网
首先,先引入依赖
然后分析一下自动配置类
● MybatisPlusAutoConfiguration 配置类,MybatisPlusProperties 配置项绑定。mybatis-plus:xxx 就是对mybatis-plus的定制
● SqlSessionFactory 自动配置好。底层是容器中默认的数据源
● mapperLocations 自动配置好的。有默认值。classpath*:/mapper/**/*.xml;任意包的类路径下的所有mapper文件夹下任意路径下的所有xml都是sql映射文件。 建议以后sql映射文件,放在 mapper下
● 容器中也自动配置好了 SqlSessionTemplate
● @Mapper 标注的接口也会被自动扫描;建议直接 @MapperScan 批量扫描就行
优点:
● 只需要我们的Mapper继承 BaseMapper 就可以拥有crud能力,只要不是特别复杂的,我们都不用在自己写映射文件了
a)在pom.xml中引入依赖
b)分析自动配置类RedisAutoConfiguration
找到配置文件的前缀
连接工厂是准备好的,LettuceConnectionConfiguration、JedisConnectionConfiguration
自动注入了RedisTemplate<Object, Object> : xxxTemplate;(操作redis,kv都是Object)
自动注入了StringRedisTemplate;k:v都是String
底层只要我们使用 StringRedisTemplate、RedisTemplate就可以操作redis
c)redis的环境搭建
1、阿里云按量付费redis。经典网络
2、申请redis的公网连接地址
3、修改白名单 允许0.0.0.0/0 访问
d)springboot操作redis
首先在配置文件中配制好相关的数据
password:用户名+:+密码
然后就可以直接使用了,下面写一个测试类
e)切换至jedis
首先导入依赖:
然后再配置文件中加上:
基本概念见:
尚硅谷单元测试
Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库
首先引入单元测试的依赖(创建springboot工程时会自动帮我们引入)
我们创建一个springboot工程后,系统就会自动为我们创建一个有@SpringBootTest注解的测试类,我们在这个类中用@Test写测试方法即可
SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖。如果需要兼容junit4需要自行引入(不能使用junit4的功能 @Test),如果像继续兼容JUnit4,需要加入的依赖
SpringBoot整合Junit以后。
● 编写测试方法:@Test标注(注意需要使用junit5版本的注解)
● Junit类具有Spring的功能,@Autowired、比如 @Transactional 标注测试方法,测试完成后自动回滚
下面是官方文档
JUnit5官方文档
在官方文档的2.1Annoations中即可找到
断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是 org.junit.jupiter.api.Assertions 的静态方法,得益于断言机制,当所有测试运行结束后,会有一个详细的运行报告(那些方法成功,哪些失败,为什么失败等等)
在项目上线前,我们先clean再test,就可以得到一份完整的测试报告
a)简单断言
用来对单个值进行简单的验证
下面的案例主要测试了assertEquals与assertSame,更多的简单断言可以在Assertions这个类中找到
b)数组断言
通过 assertArrayEquals 方法来判断两个对象或原始类型的数组是否相等
c)组合断言
assertAll 方法接受多个 org.junit.jupiter.api.Executable 函数式接口的实例作为要验证的断言,只有全部通过,才会继续往下走
d)异常断言
在JUnit4时期,想要测试方法的异常情况时,需要用@Rule注解的ExpectedException变量还是比较麻烦的。而JUnit5提供了一种新的断言方式Assertions.assertThrows() ,配合函数式编程就可以进行使用。
作用:断定业务逻辑一定会出现异常,若正常运行,则报错
e)超时断言
Junit5还提供了Assertions.assertTimeout() 为测试方法设置了超时时间
f)快速失败
通过 fail 方法直接使得测试失败
JUnit 5 中的前置条件类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止。前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要。
使用Assumptions这个包
经过看测试报告,我们发现因为不满足前置条件,这个测试方法被跳过了
重点有两个:
1.外层的test不能驱动内层的Before(After)Each/All之类的方法运行
2.内层的test可以驱动外层的Before(After)Each/All之类的方法运行
有了嵌套测试,在直接运行测试类时,就能很方便的看出层级关系
参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利。
@ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
@NullSource: 表示为参数化测试提供一个null的入参
@EnumSource: 表示为参数化测试提供一个枚举入参
@CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
@MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)
更多细节可以参考官方文档的Migrating from JUnit 4
在进行迁移的时候需要注意如下的变化:
注解在 org.junit.jupiter.api 包中,断言在 org.junit.jupiter.api.Assertions 类中,前置条件在 org.junit.jupiter.api.Assumptions 类中。
把@Before 和@After 替换成@BeforeEach 和@AfterEach。
把@BeforeClass 和@AfterClass 替换成@BeforeAll 和@AfterAll。
把@Ignore 替换成@Disabled。
把@C