SpringBoot2
HelloWorld
引入依赖
1 |
|
创建主程序
1 | /** |
编写业务
1 | /** |
启动
无需再配置tomcat。
简化配置
新建配置文件,所有的配置都可以在里面更改。
简化部署
SpringBoot不需要设置打包方式
1)引入插件
1 | <build> |
2)执行clean、package打包
3)运行jar包
4)效果
SpringBoot特点
依赖管理
- 父项目做依赖管理
1 | <!-- 依赖管理 --> |
- 无需关注版本号,自动版本仲裁
1 | 1、引入依赖默认都可以不写版本 |
- 修改默认版本号
1 | 1、查看spring-boot-dependencies里面规定当前依赖的版本 用的 key。 |
自动配置
自动配置Tomcat
- 引入Tomcat依赖
- 配置Tomcat
自动配置SpringMVC
- 引入SpringMVC全套组件
- 自动配置常用组件
自动配好web常用功能
- 自动配好SpringMVC常见问题:如字符编码问题
默认的包结构
主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来,所以项目结构如下图所示:
无需以前的包扫描配置
想要改变扫描路径,
@SpringBootApplication(scanBasePackages=**"com.cuc"**)
或@ComponentScan
指定扫描路径1
2
3
4
5
等同于
各种配置拥有默认值
- 默认配置最终都是映射到某个类上,如:MultipartProperties
- 配置文件的值最终会绑定每个类上,这个类会在容器中创建对象
按需加载所有自动配置项
- 非常多的starter
- 引入了哪些场景这个场景的自动配置才会开启
- SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure 包里面
… …
底层注解
@Configuration
主要理解proxyBeanMethods的使用,但是我没理解。
1)配置类
1 | /** |
2)主程序类
1 | /** |
@Import
给容器导入组件,其它高级用法
1 | // 这是一个配置类,相当于配置文件 |
@Conditional
按条件装配。满足指定条件则进行组件注入
示例:
1 |
|
@ImportResource
导入配置文件。例如自己写的xml配置文件,可以使用@ImportResource使之生效
1)首先创建一个spring配置文件
1 |
|
2)使用注解导入配置文件
1 |
|
3)测试
1 | /** |
@ConfigurationProperties
配置绑定,读取properties文件中内容,并且把它封装到JavaBean中,以供随时使用。
1)填写properties文件内容
2)封装容器中
- 第一种方式:
@Component
+@ConfigurationProperties
1 | // 需将当前类加到容器中 |
- 第二种方式 :
@ConfigurationProperties
+@EnableConfigurationProperties
1 |
|
1 |
|
3)编写controller类
1 |
|
4)测试
SpringBoot开发
引入依赖
地址,查看SpringBoot支持的依赖。
修改配置项
例如修改端口号等
地址,查看SpringBoot的配置项。
自定义加入或替换组件
@Bean、@Component等
自定义器
XXXXXCustomizer;有待深入
…
开发小技巧
LomBok
简化JavaBean开发,以及日志功能
引入依赖
1
2
3
4<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>安装插件
使用
1
2
3
4
5
6
7
8
9
10
11// 生成get、set、toString、equals、hashCode方法
// get方法
// set方法
// 生成toString方法
// 无参构造
// 有参构造(全参)
// 生成equals和hashCode方法
public class Emp {
private String name;
private Integer age;
}1
2
3
4
5
6
7
8
9
10// 日志
public class HelloController {
public String handler01(){
log.info("请求进来了");
return "Hello World!";
}
}
Spring Initailizr
SpringBoot初始化向导,帮助快速创建SpringBoot应用
先删除没用的东西
自动生成目录结构
dev-tools
引入依赖
1
2
3
4
5<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>使用
当更改了项目内容,执行
Ctrl+F9
实现自动重启。
配置文件
配置文件:properties、yaml,会先读取properties然后再读取yaml
yaml
才发现hexo静态博客的配置文件就是yaml
基本语法
key: value;kv之间有空格
大小写敏感
使用缩进表示层级关系
缩进不允许使用tab,只允许空格
缩进的空格数不重要,只要相同层级的元素左对齐即可
‘#’表示注释
字符串无需加引号,如果要加,’’ 和 “” 表示字符串内容,以及单引号里写
\n
仅仅代表字符串,而双引号则换行所以:单引号不会转义,双引号会转义。
数据类型
字面量:单个的、不可再分的值。date、boolean、string、number、null
1
k: v
对象:map、hash、set、object
1
2
3
4
5
6行内写法: k: {k1:v1,k2:v2,k3:v3}
#或
k:
k1: v1
k2: v2
k3: v3数组:array、list、queue
1
2
3
4
5
6行内写法: k: [v1,v2,v3]
#或者
k:
- v1
- v2
- v3
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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;
}
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# yaml表示以上对象
# 创建application.yml配置文件并写入
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
2
3
4
5
6
7
private Person person;
public Person handler03(){
return person;
}
配置提示
自定义的类和配置文件绑定一般没有提示。
引入依赖
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<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- 以及 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<!-- 配置打包时不将它进行打包,应为这是跟业务无关的,只是为了增加效率 -->
<exclude>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>使用
Web场景
简单功能
静态资源访问
静态资源目录
SpringBoot默认静态资源目录为
/static
or/public
or/resources
or/META-INF/resources
也就是在resources(即类目录)下创建对应名称的文件夹,SpringBoot就会自动将它识别为静态资源的存储位置
访问: 当前项目根路径/ + 静态资源名
原理:请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面
改变默认的静态资源路径
1
2
3spring:
resources:
static-locations: classpath:/haha # 可以配置多个 [classpath:/haha,...]效果
静态资源访问前缀
修改访问前缀(默认无前缀)访问:
当前项目根路径 + static-path-pattern + 静态资源名
1
2
3spring:
mvc:
static-path-pattern: /res/**webjars(了解)
自动映射 /webjars/**
1
2
3
4
5
6
7<!-- 举例 -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.5.1</version>
</dependency>访问地址:http://localhost:8080/webjars/jquery/3.5.1/jquery.js 后面地址要按照依赖里面的包路径
欢迎页
静态资源路径下 index.html
- 可以配置静态资源路径
- 但是不可以配置静态资源的访问前缀。否则导致index.html不能被访问
1 | spring: |
自定义Favicon
favicon.ico
放在静态资源目录下即可。同样访问前缀不能有
1 | spring: |
请求处理
请求映射
@xxxMapping,支持Rest风格,需手动配置开启
HiddenHttpMethodFilter
,才能支持PUT、DELETE
1 | # 开启 |
1 | <!-- 1、method必须为post --> |
1 |
|
拓展:自定义_method
名称
1 |
|
1 | <input name="_m" type="hidden" value="PUT" /> |
参数与注解
注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// http://localhost:8080/car/2/owner/admin?age=18&inters=aa&inters=bb
public Object getCar( Integer id,
String name,
2, username=admin} Map<String,String> pv, // {id=
String userAgent,
Map<String,String> header, // 所有请求头信息
Integer age,
List<String> inters, // [aa, bb]
18, inters=aa} Map<String,String> params, // {age=
String _ga,
Cookie cookie)
{
return null;
}获取请求体,即post请求,获取kv值。
1
2
3
4
5<form action="/save" method="post">
<input name="username" value="admin" />
<input name="root" value="root" />
<input value="提交" type="submit" />
</form>1
2
3
4
5
6
7
public Object postMethod({ String content)
Map<String,Object> map = new HashMap<>();
map.put("content",content);
System.out.println(map); // {content=username=admin&root=root}
return null;
}@RequestAttribute
获取请求域数据,@ModelAttribute
没学1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class RequestController {
public String gotoPage(HttpServletRequest request){
request.setAttribute("msg","成功...");
return "forward:/success";
}
public String success({ String msg,HttpServletRequest request)
System.out.println(msg);
// 等价
System.out.println(request.getAttribute("msg"));
return null;
}
}矩阵变量
- 语法:
- 矩阵变量应当绑定在路径变量中
- 若有多个矩阵变量,矩阵变量之间用
;
分隔 - 若一个矩阵变量有多个值,值之间使用
,
分隔,或者命名多个重复key
- SpringBoot默认禁用了矩阵变量的功能,需手动开启
- 矩阵变量必须有url路径变量才能被解析
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
public class WebConfig implements WebMvcConfigurer {
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
// 取消自动移除路径;后面的内容
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
// 使用@Bean注入或者通过实现WebMvcConfigurer
// @Bean
// public WebMvcConfigurer webMvcConfigurer(){
// return new WebMvcConfigurer() {
// @Override
// public void configurePathMatch(PathMatchConfigurer configurer) {
// UrlPathHelper urlPathHelper = new UrlPathHelper();
// urlPathHelper.setRemoveSemicolonContent(false);
// configurer.setUrlPathHelper(urlPathHelper);
// }
// };
// }
}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// http://localhost:8080/cars/sell;low=34;brand=byd,audi
// {path}是必须的,不能写成sell
public Object carsSell( Integer low,
List<String> brand,
String path)
{
Map<String,Object> map = new HashMap<>();
map.put("low",low);
map.put("brand",brand);
map.put("path",path);
System.out.println(map); // {path=sell, low=34, brand=[byd, audi]}
return null;
}
// http://localhost:8080/boss/1;age=20/2;age=10
public Object boss( Integer bossAge,
{ Integer empAge)
Map<String,Object> map = new HashMap<>();
map.put("bossAge",bossAge);
map.put("empAge",empAge);
System.out.println(map); // {bossAge=20, empAge=10}
return null;
}- 语法:
将参数封装到pojo
使用GET或POST都可以
1
2
3
4
5
6
7
8<form action="/saveUser" method="post">
姓名:<input name="userName" value="张三" /><br>
年龄:<input name="age" value="18" /><br>
生日:<input name="birth" value="2022/07/08" /><br>
宠物姓名:<input name="pet.name" value="阿猫" /><br>
宠物年龄:<input name="pet.age" value="5" /><br>
<input value="保存" type="submit" />
</form>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// User
public class User {
private String userName;
private Integer age;
private Date birth;
private Pet pet;
}
// Pet
public class Pet {
private String name;
private Integer age;
}1
2
3
4
5
6
public Object saveUser(User user){
System.out.println(user);
// User(userName=张三, age=18, birth=Fri Jul 08 00:00:00 CST 2022, pet=Pet(name=阿猫, age=5))
return null;
}自定义Converter
自定义参数封装到pojo的规则
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
public class WebConfig {
// 大体步骤基本跟开启矩阵变量一致
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
public void addFormatters(FormatterRegistry registry) {
// 实现将字符串转成Pet
registry.addConverter(new Converter<String, Pet>() {
public Pet convert(String s) { // s 就是页面提交过来的值
// 阿猫,3
if(!StringUtils.isEmpty(s)){
Pet pet = new Pet();
String[] split = s.split(",");
pet.setName(split[0]);
pet.setAge(Integer.parseInt(split[1]));
return pet;
}
return null;
}
});
}
};
}
}1
2
3
4
5
6
7
8
9<form action="/saveUser" method="post">
姓名:<input name="userName" value="张三" /><br>
年龄:<input name="age" value="18" /><br>
生日:<input name="birth" value="2022/07/08" /><br>
<!--宠物姓名:<input name="pet.name" value="阿猫" /><br>-->
<!--宠物年龄:<input name="pet.age" value="5" /><br>-->
宠物:<input name="pet" value="阿猫,5" /><br>
<input value="保存" type="submit" />
</form>1
2// 结果是正确的
User(userName=张三, age=18, birth=Fri Jul 08 00:00:00 CST 2022, pet=Pet(name=阿猫, age=5))
数据响应与内容协商
响应JSON
jackson.jar+@ResponseBody,spring-boot-starter-web依赖就包含了jackson
内容协商
根据客户端接收能力的不同,返回不同媒体类型的数据(xml、json)。其中优先接收xml。控制器方法直接通过@ResponseBody返回数据即可。
引入依赖
1
2
3
4<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>接收数据(根据请求头响应相应格式数据)
开启浏览器参数方式内容协商功能
为了方便内容协商,开启基于请求参数的内容协商功能。因为浏览器默认优先接收xml,开启后,浏览器可根据需要获取指定格式内容。
1
2
3
4spring:
mvc:
contentnegotiation:
favor-parameter: true #开启请求参数内容协商模式发请求: http://localhost:8080/xmlOrJson?format=json 或 http://localhost:8080/xmlOrJson?format=xml
自定义MessageConverter
返回自定义协议数据,例如请求头为
application/x-test
,其中x-test
自定义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/**
* 自定义Converter
*/
public class TestConverter implements HttpMessageConverter<User> {
/**
* 读功能,即能够接收对应格式数据
*/
public boolean canRead(Class<?> aClass, MediaType mediaType) {
return false;
}
/**
* 写功能,即能够返回对应格式数据
*/
public boolean canWrite(Class<?> aClass, MediaType mediaType) {
return aClass.isAssignableFrom(User.class);
}
/**
* 服务器统计所有MessageConverter都能写出哪些内容类型(xml、json...)
* application/x-test
*/
public List<MediaType> getSupportedMediaTypes() {
return MediaType.parseMediaTypes("application/x-test");
}
public User read(Class<? extends User> aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException {
return null;
}
public void write(User user, MediaType mediaType, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {
// 自定义协议数据的写出
String data = user.getUserName()+";"+user.getBirth();
// 写出去
OutputStream body = httpOutputMessage.getBody();
body.write(data.getBytes());
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class WebConfig {
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
// 自定义内容协商(extend表示扩展,并不会把已支持的覆盖掉)
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new TestConverter());
}
};
}
}1
2
3
4
5
6
7
8
9
public User returnXTest(){
System.out.println("ss");
User user = new User();
user.setUserName("admin");
user.setBirth(new Date());
return user;
}自定义参数内容协商
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
public class WebConfig {
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
// 自定义内容协商(extend表示扩展,并不会把已支持的覆盖掉)
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new TestConverter());
}
// 自定义参数内容协商
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
Map<String, MediaType> mediaTypes = new HashMap<>();
// 指定支持解析哪些参数对应哪些媒体类型
mediaTypes.put("json",MediaType.APPLICATION_JSON);
mediaTypes.put("xml",MediaType.APPLICATION_XML);
// 前提是之前已经自定义了converter
mediaTypes.put("gg",MediaType.parseMediaType("application/x-test"));
ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(mediaTypes);
configurer.strategies(Arrays.asList(strategy));
}
};
}
}完善自定义参数内容协商
使用自定义参数内容协商,通过请求头内容协商的方法将失效,即默认返回json数据,而不是根据请求头返回对应格式数据。
可如下解决
1
2
3
4
5
6
7
8
9
10
11
12
13
14// 自定义参数内容协商
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
Map<String, MediaType> mediaTypes = new HashMap<>();
mediaTypes.put("json",MediaType.APPLICATION_JSON);
mediaTypes.put("xml",MediaType.APPLICATION_XML);
mediaTypes.put("gg",MediaType.parseMediaType("application/x-test"));
// 基于请求参数
ParameterContentNegotiationStrategy ParameterStrategy = new ParameterContentNegotiationStrategy(mediaTypes);
// 基于请求头
HeaderContentNegotiationStrategy headerStrategy = new HeaderContentNegotiationStrategy();
configurer.strategies(Arrays.asList(ParameterStrategy,headerStrategy));
}之后不管用请求头还是请求参数都可以,请求参数的优先级好像更高。
视图解析与模板引擎
SpringBoot默认不支持JSP,需要引入第三方模板引擎技术实现页面渲染
视图解析
转发、重定向、自定义视图
模板引擎-Thymeleaf
先了解
1 | <dependency> |
1 |
|
拦截器
登录检查与静态资源放行
- 拦截器类
1 | /** |
- 配置拦截器
1 |
|
- 控制器方法
1 | // 需先访问一次/login再访问/main才能去往首页 |
拦截器原理
- 根据当前请求,找到
HandlerExecutionChain
- 先
顺序执行
所有拦截器的preHandle
方法- 1、如果当前拦截器prehandler返回为true。则执行下一个拦截器的preHandle
- 2、如果当前拦截器返回为false。直接
倒序
执行所有已经执行了的拦截器的afterCompletion;
- 如果任何一个拦截器返回false。直接跳出不执行目标方法
- 所有拦截器都返回True。执行目标方法
- 倒序执行所有拦截器的postHandle方法。
- 前面的步骤有任何异常都会直接倒序触发 afterCompletion
- 页面成功渲染完成以后,也会倒序触发 afterCompletion
文件上传
单文件上传与多文件上传
页面表单
1
2
3
4
5
6
7
8
9<form method="post" action="/upload" enctype="multipart/form-data">
<input type="email" name="email"><br>
<input type="text" name="username"><br>
<!-- 单文件 -->
<input type="file" name="headerImg"><br>
<!-- 多文件 -->
<input type="file" name="files" multiple><br>
<input type="submit" value="提交">
</form>文件上传代码
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/**
* MultipartFile 自动封装上传过来的文件
*/
public String upload( String email,
String username,
MultipartFile headerImg,
MultipartFile[] files)throws IOException {
log.info("上传的信息:email={},username={},headerImg={},files={}",
email,username,headerImg.getSize(),files.length);
if(!headerImg.isEmpty()){
//保存到文件服务器,OSS服务器
String originalFilename = headerImg.getOriginalFilename();
headerImg.transferTo(new File("D:\\fileImg\\"+originalFilename));
}
if(files.length > 0){
for (MultipartFile file : files) {
if(!file.isEmpty()){
String originalFilename = file.getOriginalFilename();
file.transferTo(new File("D:\\fileUpload\\"+originalFilename));
}
}
}
return "success";
}
异常处理
默认规则
默认情况下,SpringBoot提供
/error
处理所有错误的映射对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据
templates/error/
下的4xx
、5xx
名称页面会被自动解析;前提得引入thymeleaf依赖。需要注意例如发生400
错误,它会自动去找400.html
页面,如果没有则找4xx.html
页面,如果再没有就是用默认的错误页面。
自定义异常处理
@ControllerAdvice
+@ExceptionHandler
处理全局异常(推荐使用)异常处理类
1
2
3
4
5
6
7
8
9
10
11
12
13
14/**
* 处理整个web controller的异常
*/
public class GlobalExceptionHandler {
// 处理数学运算以及空指针异常
public String handleException01(Exception e){
log.error("异常:{}",e.getMessage());
return "error/404"; // 视图地址
}
}控制器方法
1
2
3
4
5
public String exception01(){
int i = 10/0;
return "";
}测试
@ResponseStatus
+自定义异常异常处理类
1
2
3
public class TooManyException extends RuntimeException{
}控制器方法
1
2
3
4
5
6
7
8
9
public String exception02(){
int i = 101;
if (i>100){
throw new TooManyException();
}
return "";
}测试
Spring底层的异常,如参数类型转换异常
自定义实现
HandlerExceptionResolver
处理异常ErrorViewResolver
实现自定义异常处理
原生组件注入
Servlet、Filter、Listener
注意:使用以下方式注入的Servlet不会被Spring的拦截器拦截!!!!
解释:
DispatcherServlet
默认处理/
后任意路径,若定义原生Tomcat-Servlet
处理路径为/my
,因为精确优先原则,请求如果为/my
直接走Tomcat-Servlet
,如果不是,再走DispatcherServlet
的Spring流程。
使用Servlet API
1 | /** |
Servlet
1
2
3
4
5
6
7
8
9
10/**
* 需要在主程序类使用@ServletComponentScan注解才能使用
*/
public class MyServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("666");
}
}Filter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// 同样需要在主程序类使用@ServletComponentScan注解才能使用
// 拦截路径
public class MyFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("初始化方法");
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 逻辑
filterChain.doFilter(servletRequest,servletResponse); // 放行
}
public void destroy() {
System.out.println("销毁");
}
}Listener
1
2
3
4
5
6
7
8
9
10
11
12
13// 主程序类同样需要@ServletComponentScan注解
public class MyListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent sce) {
System.out.println("监听到项目初始化完成");
}
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("监听到项目销毁");
}
}
使用RegistrationBean
将上面的@WebServlet、@WebFilter、@WebListener注解删掉,使用如下代替
1 |
|
嵌入式Web容器
了解下
默认支持的webServer
Tomcat
、Jetty
、Undertow
ServletWebServerApplicationContext 容器启动寻找ServletWebServerFactory 并引导创建服务器
切换服务器
默认为Tomcat
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!-- 将tomcat排除掉 -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 切换 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
数据访问
SQL
配置
导入JDBC场景(自动配置事务、数据源-HikariDataSource、jdbc、jndi、以及分布式事务相关的依赖)
1
2
3
4<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>导入MySQL驱动
1
2
3
4
5<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>添加配置项
1
2
3
4
5
6spring:
datasource:
url: jdbc:mysql://localhost:3306/db_springboot?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver测试
1
2
3
4
5
6
7
8
9
10
11
12
13
class Boot02HelloworldApplicationTests {
JdbcTemplate jdbcTemplate;
void contextLoads() {
Long aLong = jdbcTemplate.queryForObject("select count(*) from t_user", Long.class);
System.out.println(aLong);
}
}
使用Druid数据源
自定义方式
引入依赖
1
2
3
4
5<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.17</version>
</dependency>配置数据源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MyDatasourceConfig {
// 配置
public DataSource dataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
// druidDataSource.setUrl("jdbc:mysql://localhost:3306/db_springboot?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8");
// druidDataSource.setUsername("root");
// druidDataSource.setPassword("root");
// druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
// 或者使用@ConfigurationProperties注解绑定配置文件中的配置
// 还可以配置很多druid配置
druidDataSource.setFilters("stat"); // 加入监控功能,同样能将配置写在配置文件中
return druidDataSource;
}
}测试
1
2
3
4
5
6
7
DataSource dataSource;
void test2(){
System.out.println("数据源类型:"+dataSource.getClass());
}StatViewServlet
StatViewServlet的用途包括:
- 提供监控信息展示的html页面
- 提供监控信息的JSON API
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyDatasourceConfig {
// 配置
...
// 配置druid的监控页
public ServletRegistrationBean statViewServlet(){
StatViewServlet statViewServlet = new StatViewServlet();
ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(statViewServlet, "/druid/*");
return registrationBean;
}
}StatFilter
用于统计监控信息;如SQL监控、URI监控
1
druidDataSource.setFilters("stat"); // 加入监控功能
其它功能去看官方文档
官方starter方式
引入依赖
1
2
3
4
5<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>示例配置
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
32spring:
datasource:
url: jdbc:mysql://localhost:3306/db_springboot?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
druid:
aop-patterns: com.cuc.boot.* #监控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
搭建
starter
SpringBoot官方的Starter:spring-boot-starter-*
第三方的: *-spring-boot-starter
建议安装插件
1 | <!-- 会自动引入jdbc,可以把之前引入的jdbc注释掉 --> |
以前配置的大体操作步骤
- 全局配置文件
- SqlSessionFactory:自动配置了
- SqlSession:自动配置了SqlSessionTemplate,里面组合了SqlSession
- Mapper:只要在自己写的
mapper接口
标注了@Mapper
就会被自动扫描(当然也可以在主程序类使用@MapperScan
指定扫描包而不用@Mapper
注解)
1 | # 配置mybatis规则 |
1 | <!-- 全局配置文件 --> |
配置模式
1 | // mapper接口 |
1 | <!-- mapper映射文件 --> |
1 | // 测试 |
注解模式
使用@Select、@Insert、@Update、@Delete、@Options等注解就可以不用在mapper映射文件中写
1 |
|
混合模式
即注解和配置模式可以混合使用。
整合Mybatis-Plus
自动配置
配置项绑定:
mybatis-plus:xxx
就是对mybatis-plus的定制SqlSessionFactory 自动配置好了。底层是容器中默认的数据源
mapperLocations 自动配置好的。有默认值。
classpath*:/mapper/**/*.xml
。容器中也自动配置好了 SqlSessionTemplate
@Mapper
标注的接口也会被自动扫描;建议直接@MapperScan("com.cuc.boot.mapper")
批量扫描就行
引入依赖
1
2
3
4
5
6<!-- 会自动引入jdbc和mybatis,所以可以将之前引入的注释掉 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>数据源配置
之前已经配置过了,即spring.datasource下的配置
使用
基础的用法都跟mybatis差不多,优点:可以继承BaseMapper,就不用写一些基本的crud,升入请学习mybatis-plus
1
2
3
4
5
6
7
8
public class Account {
// 属性名需跟字段名一样
private int id;
private String username;
private String password;
}1
2
3
4
public interface AccountMapper extends BaseMapper<Account> {
}1
2
3
4
5
void test3(){
Account acc = accountMapper.selectById(1);
System.out.println(acc);
}
NoSQL
Redis
配置
引入依赖
1
2
3
4<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>redis环境
默认已经搭建或者购买了现成reids,并且做了相关配置。
配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13spring:
redis:
host: 120.48.54.126 # Redis服务器地址
port: 6379 #Redis服务器连接端口
password: root # 密码
database: 0 #Redis数据库索引(默认为0)
timeout: 1800000 #连接超时时间(毫秒)
lettuce: # 默认使用Lettuce
pool:
max-active: 20 #连接池最大连接数(使用负值表示没有限制)
max-wait: 1 #最大阻塞等待时间(负数表示没限制)
max-idle: 5 #连接池中的最大空闲连接
min-idle: 0 #连接池中的最小空闲连接测试
1
2
3
4
5
6
7
8
9
10
11
private StringRedisTemplate redisTemplate;
void testRedis() {
//设置值到redis
redisTemplate.opsForValue().set("name","lucy");
//从redis获取值
String name = (String)redisTemplate.opsForValue().get("name");
System.out.println(name); // lucy
}
Jedis
引入依赖
1
2
3
4
5<!--导入jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14spring:
redis:
host: 120.48.54.126
port: 6379
password: root
client-type: jedis # 指定客户端类型
database: 0
timeout: 1800000
jedis: # 同样修改
pool:
max-active: 20
max-wait: 1
max-idle: 5
min-idle: 0查看是否切换
1
2
3
4
5
6
7
8
RedisConnectionFactory redisConnectionFactory;
void testJedis(){
System.out.println(redisConnectionFactory.getClass());
// class org.springframework.data.redis.connection.jedis.JedisConnectionFactory
}
单元测试
JUnit5简介与引入
Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
注意:
SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖。如果需要兼容junit4需要自行引入
1 | <dependency> |
1 | <!-- 如果需要使用junit4 --> |
现在版本:
1 |
|
以前:@SpringBootTest + @RunWith(SpringTest.class)
常用注解
- @Test : 表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
- @ParameterizedTest : 表示方法是参数化测试
- @RepeatedTest : 表示方法可重复执行
- @DisplayName : 为测试类或者测试方法设置展示名称
- @BeforeEach : 表示在每个单元测试之前执行
- @AfterEach : 表示在每个单元测试之后执行
- @BeforeAll : 表示在所有单元测试之前执行
- @AfterAll : 表示在所有单元测试之后执行
- @Tag : 表示单元测试类别,类似于JUnit4中的@Categories
- @Disabled : 表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
- @Timeout : 表示测试方法运行如果超过了指定时间将会返回错误
- @ExtendWith : 为测试类或测试方法提供扩展类引用
1 |
|
断言
断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证。
即断定某件事情发生,如果没发生就认为出错。
简单断言
用来对单个值进行简单的验证。如:
方法 | 说明 |
---|---|
assertEquals | 判断两个对象或两个原始类型是否相等 |
assertNotEquals | 判断两个对象或两个原始类型是否不相等 |
assertSame | 判断两个对象引用是否指向同一个对象 |
assertNotSame | 判断两个对象引用是否指向不同的对象 |
assertTrue | 判断给定的布尔值是否为 true |
assertFalse | 判断给定的布尔值是否为 false |
assertNull | 判断给定的对象引用是否为 null |
assertNotNull | 判断给定的对象引用是否不为 null |
1 | // 以下断言都是正确的 |
数组断言
通过 assertArrayEquals 方法来判断两个对象或原始类型的数组是否相等
1 |
|
组合断言
1 |
|
异常断言
1 |
|
超时断言
1 |
|
快速失败
通过 fail 方法直接使测试失败
1 |
|
前置条件
JUnit 5 中的前置条件类似于断言,不同之处在于
不满足的断言会使得测试方法失败
,而不满足的前置条件只会使得测试方法的执行终止
。前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要。
1 | // assumeTrue 和 assumFalse 确保给定的条件为 true 或 false,不满足条件会使得测试执行终止。 |
嵌套测试
JUnit 5 可以通过 Java 中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。嵌套的层次没有限制。
1 | class TestingAStackDemo { |
参数化测试
参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利。
强大之处:如:CSV,YML,JSON 文件甚至方法的返回值也可以作为入参。只需要去实现ArgumentsProvider接口,任何外部文件都可以作为它的入参。
- @ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
- @NullSource: 表示为参数化测试提供一个null的入参
- @EnumSource: 表示为参数化测试提供一个枚举入参
- @CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
- @MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)
1 |
|
指标监控
SpringBoot Actuator
简介
未来每一个微服务在云上部署以后,我们都需要对其进行监控、追踪、审计、控制等。SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能。
使用
引入场景
1
2
3
4<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>暴露所有监控信息为HTTP
1
2
3
4
5
6management:
endpoints:
enabled-by-default: true #暴露所有端点信息
web:
exposure:
include: '*' #以web方式暴露测试
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
… …效果
谷歌安装json插件后
可视化
https://github.com/codecentric/spring-boot-admin
Actuator Endpoint
端点
最常用的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可以为平台返回当前应用的一系列组件健康状况的集合。
- health endpoint返回的结果,是一系列健康检查后的一个汇总报告
- 很多的健康检查默认已经自动配置好了,比如:数据库、redis等
- 可以很容易的添加自定义的健康检查机制
Metrics Endpoint
提供详细的、层级的、空间指标信息,这些信息可以被pull(主动推送)或者push(被动获取)方式得到
- 通过Metrics对接多种监控系统
- 简化核心Metrics开发
- 添加自定义Metrics或者扩展已有Metrics
管理Endpoints
开启与禁用Endpoints
默认所有的Endpoint除过shutdown都是开启的。
需要开启或者禁用某个Endpoint。配置模式为 management.endpoint.endpointName.enabled = true
1
2
3
4management:
endpoint:
beans:
enabled: true或者禁用所有的Endpoint然后手动开启指定的Endpoint
1
2
3
4
5
6
7
8management:
endpoints:
enabled-by-default: false
endpoint:
beans:
enabled: true
health:
enabled: true
暴露Endpoints
支持的暴露方式
- 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
1 |
|
1 | management: |
1 |
|
常用两种方式,http://localhost:8080/actuator/info 会输出以下方式返回的所有info信息
编写配置文件
1
2
3
4
5info:
appName: boot-admin
version: 2.0.1
mavenProjectName: @project.artifactId@ #使用@@可以获取maven的pom文件值
mavenProjectVersion: @project.version@编写InfoContributor
1
2
3
4
5
6
7
8
9
public class ExampleInfoContributor implements InfoContributor {
public void contribute(Info.Builder builder) {
builder.withDetail("example",
Collections.singletonMap("key", "value"));
}
}
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class MyService{
Counter counter;
public MyService(MeterRegistry meterRegistry){
counter = meterRegistry.counter("myservice.method.running.counter");
}
public void hello() {
counter.increment();
}
}
//也可以使用下面的方式
MeterBinder queueSize(Queue queue) {
return (registry) -> Gauge.builder("queueSize", queue::size).register(registry);
}
场景:开发ReadinessEndpoint来管理程序是否就绪,或者LivenessEndpoint来管理程序是否存活;
1 |
|