springboot基础知识

springboot基础知识

一、Springboot简介

路线:

javase:OOP

javaweb:独立开发MVC三层架构的网站:原始

ssm:框架:简化了我们的开发流程,配置也开始较为复杂;

在此之前项目打包都是war包,程序在Tomcat中运行

spring再简化:springBoot-jar包,内嵌Tomcat;微服务架构!

服务越来越多:springCloud

1、回顾spring

Spring是一个开源的java框架,主要采用了4中关键策略

1、基于POJO的轻量级和最小侵入性编程,所有东西都是bean;

2、通过IOC,依赖注入(DI)和面向接口实现松耦合;

3、基于切面(AOP)和惯例进行声明式编程;

4、通过切面和模版减少样式代码,RedisTemplate,xxxTemplate;

2、什么是springboot

springboot就是一个javaweb的开发框架,和SpringMVC类似,对比其他javaweb框架的好处,官方说是简化开发,约定大于配置, you can “just run”,能迅速的开发web应用,几行代码开发一个http接口。

Spring Boot 以约定大于配置的核心思想,默认帮我们进行了很多设置,多数 Spring Boot 应用只需要很少的 Spring 配置。同时它集成了大量常用的第三方库配置(例如 Redis、MongoDB、Jpa、RabbitMQ、Quartz 等等),Spring Boot 应用中这些第三方库几乎可以零配置的开箱即用。

简单来说就是SpringBoot其实不是什么新的框架,它默认配置了很多框架的使用方式,就像maven整合了所有的jar包,spring boot整合了所有的框架 。

二、第一个SpringBoot程序

构建方式:

  1. 使用 IDEA 直接创建项目
  2. 使用Spring Initializr 的 Web页面创建项目

项目结构:

  • 一个程序的主启动类(程序的主入口)
  • 一个application.properties 配置文件(SpringBoot的核心配置文件)
  • 一个测试类
  • 一个pom.xml
    • 项目的元数据信息:创建时输入的Project Metadata部分,即Maven项目的基本元素
    • parent:继承spring-boot-starter-parent的依赖管理,控制版本与打包等内容
    • dependencies:项目具体依赖,这里包含了spring-boot-starter-web用于实现HTTP接口(该依赖中包含了Spring MVC),使用Tomcat作为默认嵌入式容器;spring-boot-starter-test用于编写单元测试的依赖包。
    • build:构建配置部分。默认使用了spring-boot-maven-plugin,配合spring-boot-starter-parent就可以把Spring Boot应用打包成JAR来直接运行。

编写一个http接口,在主程序的同级目录下,新建一个controller包,创建一个HelloController类

1
2
3
4
5
6
7
@RestController
public class ClassController {
@GetMapping("/hello")
public String hello(){
return "hello world";
}
}

可以在application.properties下配置端口

1
server.port = 8081

在浏览器中访问:

image-20221202152822133

三、Springboot配置

1、Springboot自动装配

启动器:即Springboot的启动场景,会自动帮我们导入web环境所有的依赖。Springboot会将所有的功能场景都变成一个个启动器。

1
2
3
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId></dependency>

主程序:

1
2
3
4
5
6
7
8
//标注这个类是一个springboot的应用
@SpringBootApplication
public class SpringbootStudyApplication {
public static void main(String[] args) {
//将springboot应用启动
SpringApplication.run(SpringbootStudyApplication.class, args);
}
}

点进@SpringBootApplication注解,发现他是一个派生注,改类注解主要实现了几个功能:

  • @ComponentScan:自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中
  • @SpringBootConfiguration:SpringBoot的配置类 ,标注在某个类上 , 表示这是一个SpringBoot的配置类;
  • @EnableAutoConfiguration :开启自动配置功能
    • @AutoConfigurationPackage:自动配置包,将主启动类的所在包及包下面所有子包里面的所有组件扫描到Spring容器
    • @Import({AutoConfigurationImportSelector.class}):自动配置导入,扫描所有具有META-INF/spring.factories的jar包

spring.factories文件下是一组一组的key=value的形式,其中一个key是EnableAutoConfiguration类的全类名,而它的value是一个xxxxAutoConfiguration的类名的列表,这些类名以逗号分隔。

总结:Springboot所有的自动配置都是在启动时扫描并加载:META-INF/spring.factories所有的配置类都在这里,但不一定生效,要判断条件是否成立,只要导入了对应的start,就有对应的启动器了,有了启动器自动装配就会生效,配置成功。

步骤:步骤:

  1. SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值
  2. 将这些值作为自动配置类导入容器 , 自动配置类就生效 , 帮我们进行自动配置工作;
  3. 以前需要我们配置的文件,springboot帮我们配置了!
  4. 整个J2EE的整体解决方案和自动配置都在springboot-autoconfigure的jar包中;
  5. 它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration)@Bean, 就是给容器中导入这个场景需要的所有组件 , 并配置好这些组件 @Configuration;
  6. 有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作;

2、Springboot配置文件

SpringBoot使用一个全局的配置文件 , 配置文件名称是固定的

  • application.properties

    • 语法结构 :key=value

    • ```xml
      server.port=8081

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10

      #### yaml

      YAML是 “YAML Ain’t a Markup Language” (YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(仍是一种标记语言)

      这种语言以数据作为中心,而不是以标记语言为重点!

      ```yaml
      server:
      port: 8081

特点:

  1. 空格不能省略
  2. 以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的。
  3. 属性和值的大小敏感

可以通过yaml文件赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
person:
name: lotus
age: 3
happy: false
birth: 2000/01/01
maps: {k1: v1,k2: v2}
lists:
- code
- girl
- music
dog:
name: 旺财
age: 1

通过注解注入到类中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
@ConfigurationProperties作用:
将配置文件中配置的每一个属性的值,映射到这个组件中;
告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定
参数 prefix = “person” : 将配置文件中的person下面的所有属性一一对应
*/
@Component //注册bean
@ConfigurationProperties(prefix = "person")
public class Person {
private String name;
private Integer age;
private Boolean happy;
private Date birth;
private Map<String,Object> maps;
private List<Object> lists;
private Dog dog;
}

3、JSR303数据校验

JSR303数据校验是用来校验输入内容的

Springboot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。我们这里来写个注解让我们的name只能支持Email格式;eg:

1
2
3
4
5
6
7
@Component //注册bean
@ConfigurationProperties(prefix = "person")
@Validated //数据校验
public class Person {
@Email(message="邮箱格式错误") // name必须是邮箱格式
private String name;
}

使用数据校验,可以保证数据的正确性

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
@NotNull(message="名字不能为空")
private String userName;
@Max(value=120,message="年龄最大不能查过120")
private int age;
@Email(message="邮箱格式错误")
private String email;

空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.

Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false

长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) string is between min and max included.

日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则

.......等等
除此以外,我们还可以自定义一些数据校验规则

4、多环境切换

profile是Spring对不同环境提供不同配置功能的支持,可以通过激活不同的环境版本,实现快速切换环境;

我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml , 用来指定多个环境版本;

例如:

application-test.properties 代表测试环境配置

application-dev.properties 代表开发环境配置

但是Springboot并不会直接启动这些配置文件,它默认使用application.properties主配置文件

1
2
#比如在配置文件中指定使用dev环境,我们可以通过设置不同的端口号进行测试;#我们启动SpringBoot,就可以看到已经切换到dev下的配置了;
spring.profiles.active=dev

可以通过yaml配置多文档快,这样就不需要创建多个配置文件了!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
server:
port: 8081
#选择要激活那个环境块
spring:
profiles:
active: prod

---
server:
port: 8083
spring:
profiles: dev #配置环境的名称


---

server:
port: 8084
spring:
profiles: prod #配置环境的名称

5、自动配置原理

配置文件到底能写什么?怎么写?SpringBoot官方文档中有大量的配置,我们无法全部记住。

我们以HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理;

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
@Configuration //表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件;

//启动指定类的ConfigurationProperties功能;
//进入这个HttpProperties查看,将配置文件中对应的值和HttpProperties绑定起来;
//并把HttpProperties加入到ioc容器中
@EnableConfigurationProperties({HttpProperties.class})

//Spring底层@Conditional注解
//根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效;
//这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(type = Type.SERVLET)

//判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnClass({CharacterEncodingFilter.class})

//判断配置文件中是否存在某个配置:spring.http.encoding.enabled;
//如果不存在,判断也是成立的
//即使我们配置文件中不配置spring.http.encoding.enabled=true,也是默认生效的;
@ConditionalOnProperty(
prefix = "spring.http.encoding",
value = {"enabled"},
matchIfMissing = true
)

public class HttpEncodingAutoConfiguration {
//他已经和SpringBoot的配置文件映射了
private final Encoding properties;
//只有一个有参构造器的情况下,参数的值就会从容器中拿
public HttpEncodingAutoConfiguration(HttpProperties properties) {
this.properties = properties.getEncoding();
}

//给容器中添加一个组件,这个组件的某些值需要从properties中获取
@Bean
@ConditionalOnMissingBean //判断容器没有这个组件?
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
return filter;
}
//。。。。。。。
}

根据当前不同的条件判断,决定这个配置类是否生效!

  • 一但这个配置类生效;这个配置类就会给容器中添加各种组件;
  • 这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;这样就可以形成我们的配置文件可以动态的修改springboot的内容。
  • 所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;
  • 配置文件能配置什么就可以参照某个功能对应的这个属性类

通俗理解:把我们原先需要在bean中手打的属性(property)封装成了一个类,然后通过yaml文件进行自动注入,而我们也可以在application.yaml文件中对这些property进行赋值。

精髓

  1. SpringBoot启动时会加载大量的自动配置类
  2. 我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中
  3. 我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在其中,我们就不需要再去手动配置了,如果不存在我们再手动配置)
  4. 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性,我们只需要在配置文件中指定这些属性即可;

XXXXAutoConfiguration:自动配置类:给容器添加组件,这些组件要赋值就需要绑定一个XXXXProperties类
XXXXProperties:里面封装配置文件中相关属性;

怎么去修改这些属性呢:说白了就是SpringBoot配置,—->.yaml、.properties这些文件

四、Springboot Web开发

1、Web开发静态资源处理

写请求非常简单,那我们要引入我们前端资源,我们项目中有许多的静态资源,比如css,js等文件,这个SpringBoot怎么处理呢?

如果我们是一个web应用,我们的main下会有一个webapp,我们以前都是将所有的页面导在这里面的,但是现在pom呢,打包方式是为jar的方式,那么这种方式SpringBoot能不能来给我们写页面呢?当然是可以的,但是SpringBoot对于静态资源放置的位置,是有规定的!

SpringBoot中,SpringMVC的web配置都在 WebMvcAutoConfiguration 这个配置类里面,该类中有很多方法,其中addResourceHandlers 添加资源处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
// 已禁用默认资源处理
logger.debug("Default resource handling disabled");
return;
}
// 缓存控制
Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
// webjars 配置
if (!registry.hasMappingForPattern("/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()))
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
}
  • 所有的 /webjars/** , 都需要去 classpath:/META-INF/resources/webjars/ 找对应的资源;

    • Webjars本质就是以jar包的方式引入我们的静态资源 , 我们以前要导入一个静态资源文件,直接导入即可。
    • 可以通过maven导入webjars,静态资源访问路径则为:http://localhost:8080/webjars/jquery/3.4.1/jquery.js
  • 静态资源映射规则

    • 我们去找staticPathPattern发现第二种映射规则 :/** , 访问当前的项目任意资源,它会去找 resourceProperties 这个类,我们可以点进去看一下分析:

    • ```java
      // 进入方法
      public String[] getStaticLocations() {

      return this.staticLocations;
      

      }
      // 找到对应的值
      private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
      // 找到路径
      private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {

      "classpath:/META-INF/resources/",
      

      “classpath:/resources/“,

      "classpath:/static/", 
      "classpath:/public/" 
      

      };

      1
      2
      3
      4
      5
      6
      7
      8

      所以得出结论,以下四个目录存放的静态资源可以被我们识别:

      ```"classpath:/META-INF/resources/"
      "classpath:/META-INF/resources/"
      "classpath:/resources/"
      "classpath:/static/"
      "classpath:/public/"

我们也可以自己通过配置文件来指定一下,哪些文件夹是需要我们放静态资源文件的,在application.properties中配置

1
spring.resources.static-locations=classpath:/coding/,classpath:/lotus/

2、Thymeleaf模板引擎

模板引擎的作用就是我们来写一个页面模板,比如有些值呢,是动态的,我们写一些表达式。而这些值,从哪来呢,就是我们在后台封装一些数据。然后把这个模板和这个数据交给我们模板引擎,模板引擎按照我们这个数据帮你把这表达式解析、填充到我们指定的位置,然后把这个数据最终生成一个我们想要的内容给我们写出去。

引入Thymeleaf引擎:

1
2
3
4
5
<!--thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

引入了Thymeleaf,那这个要怎么使用呢?前面介绍了Springboot自动装配原理,所以我们去看一下这个Thymeleaf的自动配置类:ThymeleafProperties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@ConfigurationProperties(
prefix = "spring.thymeleaf"
)
public class ThymeleafProperties {
private static final Charset DEFAULT_ENCODING;
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";
private boolean checkTemplate = true;
private boolean checkTemplateLocation = true;
private String prefix = "classpath:/templates/";
private String suffix = ".html";
private String mode = "HTML";
private Charset encoding;
}

我们可以在其中看到默认的前缀和后缀!

只需要把我们的html页面放在类路径下的templates下,thymeleaf就可以帮我们自动渲染了。

使用thymeleaf什么都不需要配置,只需要将他放在指定的文件夹下即可!

测试:

1、编写一个TestController

1
2
3
4
5
6
7
8
9
@Controller
public class TestController {

@RequestMapping("/t1")
public String test1(){
//classpath:/templates/test.html
return "test";
}
}

2、编写一个测试页面 test.html 放在 templates 目录下

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>test</h1>
</body>
</html>

3、在浏览器中访问

image-20221203163012098

Thymeleaf语法

官网:https://www.thymeleaf.org/

小试牛刀:

1、修改测试请求,增加数据传输

1
2
3
4
5
6
@RequestMapping("/test")
public String test(Model model){
model.addAttribute("msg","hello,springboot");
model.addAttribute("users", Arrays.asList("Lotus","Tim"));
return "test";
}

2、编写html也买安

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<!-- 要使用thymeleaf,需要在html文件中导入命名空间的约束,方便提示。 -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--所有的html元素都可以被thymeleaf替换阶段 格式: th:元素名 -->
<h1 th:text="${msg}"></h1>
<hr>
<h3 th:each="user:${users}" th:text="${user}"></h3>
<br>
<h3 th:each="user:${users}" >[[${user}]]</h3>
</body>
</html>

3、浏览器中访问

image-20221203163712711

Thymeleaf 常用语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
Simple expressions:(表达式语法)
Variable Expressions: ${...}:获取变量值;OGNL
1)、获取对象的属性、调用方法
2)、使用内置的基本对象:#18
#ctx : the context object.
#vars: the context variables.
#locale : the context locale.
#request : (only in Web Contexts) the HttpServletRequest object.
#response : (only in Web Contexts) the HttpServletResponse object.
#session : (only in Web Contexts) the HttpSession object.
#servletContext : (only in Web Contexts) the ServletContext object.

3)、内置的一些工具对象:
      #execInfo : information about the template being processed.
      #uris : methods for escaping parts of URLs/URIs
      #conversions : methods for executing the configured conversion service (if any).
      #dates : methods for java.util.Date objects: formatting, component extraction, etc.
      #calendars : analogous to #dates , but for java.util.Calendar objects.
      #numbers : methods for formatting numeric objects.
      #strings : methods for String objects: contains, startsWith, prepending/appending, etc.
      #objects : methods for objects in general.
      #bools : methods for boolean evaluation.
      #arrays : methods for arrays.
      #lists : methods for lists.
      #sets : methods for sets.
      #maps : methods for maps.
      #aggregates : methods for creating aggregates on arrays or collections.
==================================================================================

Selection Variable Expressions: *{...}:选择表达式:和${}在功能上是一样;
Message Expressions: #{...}:获取国际化内容
Link URL Expressions: @{...}:定义URL
Fragment Expressions: ~{...}:片段引用表达式

Literals(字面量)
Text literals: 'one text' , 'Another one!' ,…
Number literals: 0 , 34 , 3.0 , 12.3 ,…
Boolean literals: true , false
Null literal: null
Literal tokens: one , sometext , main ,…

Text operations:(文本操作)
String concatenation: +
Literal substitutions: |The name is ${name}|

Arithmetic operations:(数学运算)
Binary operators: + , - , * , / , %
Minus sign (unary operator): -

Boolean operations:(布尔运算)
Binary operators: and , or
Boolean negation (unary operator): ! , not

Comparisons and equality:(比较运算)
Comparators: > , < , >= , <= ( gt , lt , ge , le )
Equality operators: == , != ( eq , ne )

Conditional operators:条件运算(三元运算符)
If-then: (if) ? (then)
If-then-else: (if) ? (then) : (else)
Default: (value) ?: (defaultvalue)

Special tokens:
No-Operation: _

3、SpringbootMVC自动配置原理

在进行项目编写前,我们还需要知道一个东西,就是SpringBoot对我们的SpringMVC还做了哪些配置,包括如何扩展,如何定制。

只有把这些都搞清楚了,我们在之后使用才会更加得心应手。途径一:源码分析,途径二:官方文档

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
文档:
Spring MVC Auto-configuration
// Spring Boot为Spring MVC提供了自动配置,它可以很好地与大多数应用程序一起工作。
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.
// 自动配置在Spring默认设置的基础上添加了以下功能:
The auto-configuration adds the following features on top of Spring’s defaults:
// 包含视图解析器
Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
// 支持静态资源文件夹的路径,以及webjars
Support for serving static resources, including support for WebJars
// 自动注册了Converter:
// 转换器,这就是我们网页提交数据到后台自动封装成为对象的东西,比如把"1"字符串自动转换为int类型
// Formatter:【格式化器,比如页面给我们了一个2019-8-10,它会给我们自动格式化为Date对象】
Automatic registration of Converter, GenericConverter, and Formatter beans.
// HttpMessageConverters
// SpringMVC用来转换Http请求和响应的的,比如我们要把一个User对象转换为JSON字符串,可以去看官网文档解释;
Support for HttpMessageConverters (covered later in this document).
// 定义错误代码生成规则的
Automatic registration of MessageCodesResolver (covered later in this document).
// 首页定制
Static index.html support.
// 图标定制
Custom Favicon support (covered later in this document).
// 初始化数据绑定器:帮我们把请求数据绑定到JavaBean中!
Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).

/*
如果您希望保留Spring Boot MVC功能,并且希望添加其他MVC配置(拦截器、格式化程序、视图控制器和其他功能),则可以添加自己
的@configuration类,类型为webmvcconfiguer,但不添加@EnableWebMvc。如果希望提供
RequestMappingHandlerMapping、RequestMappingHandlerAdapter或ExceptionHandlerExceptionResolver的自定义
实例,则可以声明WebMVCregistrationAdapter实例来提供此类组件。
*/
If you want to keep Spring Boot MVC features and you want to add additional MVC configuration
(interceptors, formatters, view controllers, and other features), you can add your own
@Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide
custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or
ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.

// 如果您想完全控制Spring MVC,可以添加自己的@Configuration,并用@EnableWebMvc进行注释。
If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc.

它告诉我们SpringBoot已经帮我们自动配置好了SpringMVC,然后自动配置了哪些东西呢?

ContentNegotiatingViewResolver 内容协商视图解析器

自动配置了ViewResolver,就是我们之前学习的SpringMVC的视图解析器;

即根据方法的返回值取得视图对象(View),然后由视图对象决定如何渲染(转发,重定向)。

我们去看看这里的源码:我们找到 WebMvcAutoConfiguration , 然后搜索ContentNegotiatingViewResolver。找到如下方法!

1
2
3
4
5
6
7
8
9
10
@Bean
@ConditionalOnBean(ViewResolver.class)
@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));
// ContentNegotiatingViewResolver使用所有其他视图解析器来定位视图,因此它应该具有较高的优先级
resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
return resolver;
}

点进这类看看!找到对应的解析视图的代码;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Nullable // 注解说明:@Nullable 即参数可为null
public View resolveViewName(String viewName, Locale locale) throws Exception {
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
List<MediaType> requestedMediaTypes = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest());
if (requestedMediaTypes != null) {
// 获取候选的视图对象
List<View> candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes);
// 选择一个最适合的视图对象,然后把这个对象返回
View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs);
if (bestView != null) {
return bestView;
}
}
// .....
}

我们继续点进去看,他是怎么获得候选的视图的呢?

getCandidateViews中看到他是把所有的视图解析器拿来,进行while循环,挨个解析!

1
Iterator var5 = this.viewResolvers.iterator();

所以得出结论:ContentNegotiatingViewResolver 这个视图解析器就是用来组合所有的视图解析器的

我们再去研究下他的组合逻辑,看到有个属性viewResolvers,看看它是在哪里进行赋值的

1
2
3
4
5
6
7
8
9
10
protected void initServletContext(ServletContext servletContext) {
// 这里它是从beanFactory工具中获取容器中的所有视图解析器
// ViewRescolver.class 把所有的视图解析器来组合的
Collection<ViewResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.obtainApplicationContext(), ViewResolver.class).values();
ViewResolver viewResolver;
if (this.viewResolvers == null) {
this.viewResolvers = new ArrayList(matchingBeans.size());
}
// ...............
}

既然它是在容器中去找视图解析器,我们是否可以猜想,我们就可以去实现一个视图解析器了呢?

我们可以自己给容器中去添加一个视图解析器;这个类就会帮我们自动的将它组合进来;我们去实现一下

1、在主程序中去写一个视图解析器

1
2
3
4
5
6
7
8
9
10
11
12
@Bean //放到bean中
public ViewResolver myViewResolver(){
return new MyViewResolver();
}

//我们写一个静态内部类,视图解析器就需要实现ViewResolver接口
private static class MyViewResolver implements ViewResolver{
@Override
public View resolveViewName(String s, Locale locale) throws Exception {
return null;
}
}

2、怎么看我们自己写的视图解析器有没有起作用

我们给 DispatcherServlet 中的 doDispatch方法 加个断点进行调试一下,因为所有的请求都会走到这个方法中

Debug一下,可以看到我们自定义的组件就被加载到容器了

image-20221203181027587

所以说,我们如果想要使用自己定制化的东西,我们只需要给容器中添加这个组件就好了!剩下的事情SpringBoot就会帮我们做了

结论:

这么多的自动配置,原理都是一样的,通过这个WebMVC的自动配置原理分析,我们要学会一种学习方式,通过源码探究,得出结论;这个结论一定是属于自己的,而且一通百通。SpringBoot的底层,大量用到了这些设计细节思想,所以,没事需要多阅读源码!

SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(如果用户自己配置@bean),如果有就用用户配置的,如果没有就用自动配置的;

如果有些组件可以存在多个,比如我们的视图解析器,就将用户配置的和自己默认的组合起来!

五、Springboot整合数据库操作

对于数据访问层,无论是 SQL(关系型数据库) 还是 NOSQL(非关系型数据库),Spring Boot 底层都是采用 Spring Data 的方式进行统一处理。

Spring Boot 底层都是采用 Spring Data 的方式进行统一处理各种数据库,Spring Data 也是 Spring 中与 Spring Boot、Spring Cloud 等齐名的知名项目。

Sping Data 官网:https://spring.io/projects/spring-data

数据库相关的启动器 :可以参考官方文档:

https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#using-boot-starter

1、整合JDBC

1、在新建项目中引用JDBC API模块、MySQL Driver模块

1
2
3
4
5
6
7
8
9
<dependency>    
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>

2、编写yaml配置文件连接数据库

1
2
3
4
5
6
spring:
datasource:
username: root
password: "123456"
driver-class-name: com.mysql.lotus.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT%2B8&useSSL=false&useUnicode=true&characterEncoding=UTF-8

3、配置完这一些东西后,我们就可以直接去使用了,因为SpringBoot已经默认帮我们进行了自动配置;去测试类测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@SpringBootTest
class Springboot03JdbcApplicationTests {

@Autowired
DataSource dataSource;

@Test
void contextLoads() throws SQLException {


System.out.println(dataSource.getClass());

Connection connection = dataSource.getConnection();

System.out.println(connection);

connection.close();

}

执行结果

image-20221216165408195

结果:我们可以看到他默认给我们配置的数据源为 : class com.zaxxer.hikari.HikariDataSource , 我们并没有手动配置

我们来全局搜索一下,找到数据源的所有自动配置都在 :DataSourceAutoConfiguration文件:

1
2
3
4
5
6
7
@Import(
{Hikari.class, Tomcat.class, Dbcp2.class, Generic.class, DataSourceJmxConfiguration.class}
)
protected static class PooledDataSourceConfiguration {
protected PooledDataSourceConfiguration() {
}
}

这里导入的类都在 DataSourceConfiguration 配置类下,可以看出 Spring Boot 2.2.5 默认使用HikariDataSource 数据源,而以前版本,如 Spring Boot 1.5 默认使用 org.apache.tomcat.jdbc.pool.DataSource 作为数据源;

HikariDataSource 号称 Java WEB 当前速度最快的数据源,相比于传统的 C3P0 、DBCP、Tomcat jdbc 等连接池更加优秀;

可以使用 spring.datasource.type 指定自定义的数据源类型,值为 要使用的连接池实现的完全限定名。

关于数据源我们并不做介绍,有了数据库连接,显然就可以 CRUD 操作数据库了。但是我们需要先了解一个对象 JdbcTemplate

JDBCTemplate

  1. 有了数据源(com.zaxxer.hikari.HikariDataSource),然后可以拿到数据库连接(java.sql.Connection),有了连接,就可以使用原生的 JDBC 语句来操作数据库;
  2. 即使不使用第三方第数据库操作框架,如 MyBatis等,Spring 本身也对原生的JDBC 做了轻量级的封装,即JdbcTemplate。
  3. 数据库操作的所有 CRUD 方法都在 JdbcTemplate 中。
  4. Spring Boot 不仅提供了默认的数据源,同时默认已经配置好了 JdbcTemplate 放在了容器中,程序员只需自己注入即可使用
  5. JdbcTemplate 的自动配置是依赖 org.springframework.boot.autoconfigure.jdbc 包下的 JdbcTemplateConfiguration 类

JdbcTemplate主要提供以下几类方法:

  • execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句;
  • update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句;
  • query方法及queryForXXX方法:用于执行查询相关语句;
  • call方法:用于执行存储过程、函数相关语句。

编写一个Controller,注入 jdbcTemplate,编写测试方法进行访问测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
@RestController
@RequestMapping("/jdbc")
public class JdbcController {

/**
* Spring Boot 默认提供了数据源,默认提供了 org.springframework.jdbc.core.JdbcTemplate
* JdbcTemplate 中会自己注入数据源,用于简化 JDBC操作
* 还能避免一些常见的错误,使用起来也不用再自己来关闭数据库连接
*/
@Autowired
JdbcTemplate jdbcTemplate;

//查询employee表中所有数据
//List 中的1个 Map 对应数据库的 1行数据
//Map 中的 key 对应数据库的字段名,value 对应数据库的字段值
@GetMapping("/list")
public List<Map<String, Object>> userList(){
String sql = "select * from employee";
List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
return maps;
}

//新增一个用户
@GetMapping("/add")
public String addUser(){
//插入语句,注意时间问题
String sql = "insert into employee(last_name, email,gender,department,birth)" +
" values ('lotus','915491406@qq.com',1,101,'"+ new Date().toLocaleString() +"')";
jdbcTemplate.update(sql);
//查询
return "addOk";
}

//修改用户信息
@GetMapping("/update/{id}")
public String updateUser(@PathVariable("id") int id){
//插入语句
String sql = "update employee set last_name=?,email=? where id="+id;
//数据
Object[] objects = new Object[2];
objects[0] = "lotus123";
objects[1] = "91549140666@qq.com";
jdbcTemplate.update(sql,objects);
//查询
return "updateOk";
}

//删除用户
@GetMapping("/delete/{id}")
public String delUser(@PathVariable("id") int id){
//插入语句
String sql = "delete from employee where id=?";
jdbcTemplate.update(sql,id);
//查询
return "deleteOk";
}

}

2、整合Druid数据源

Druid简介:Java程序很大一部分要操作数据库,为了提高性能操作数据库的时候,又不得不使用数据库连接池。Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0、DBCP 等 DB 池的优点,同时加入了日志监控

Druid 可以很好的监控 DB 池连接和 SQL 的执行情况,天生就是针对监控而生的 DB 连接池。

Github地址:https://github.com/alibaba/druid/

(1)配置数据源

1、添加上 Druid 数据源依赖

1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>

2、切换数据源:之前已经说过 Spring Boot 2.0 以上默认使用 com.zaxxer.hikari.HikariDataSource 数据源,但可以 通过 spring.datasource.type 指定数据源。

1
2
3
4
5
6
7
spring:
datasource:
username: root
password: "000000"
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT%2B8&useSSL=false&useUnicode=true&characterEncoding=UTF-8&allowPublicKeyRetrieval=true
type: com.alibaba.druid.pool.DruidDataSource

3、数据源切换之后,在测试类中注入 DataSource,然后获取到它,输出一看便知是否成功切换;

image-20221216170507691

4、切换成功,可以设置数据源连接初始化大小、最大连接数、等待时间、最小连接数 等设置项;可以查看源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#Spring Boot 默认是不注入这些属性值的,需要自己绑定
#druid 数据源专有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true

#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
#如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority
#则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

5.看到需要用到log4j,所以需要在pom中导入log4j的依赖

1
2
3
4
5
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

6、现在需要程序员自己为 DruidDataSource 绑定全局配置文件中的参数,再添加到容器中,而不再使用 Spring Boot 的自动生成了;我们需要 自己添加 DruidDataSource 组件到容器中,并绑定属性;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Configuration
public class DruidConfig {

/*
将自定义的 Druid数据源添加到容器中,不再让 Spring Boot 自动创建
绑定全局配置文件中的 druid 数据源属性到 com.alibaba.druid.pool.DruidDataSource从而让它们生效
@ConfigurationProperties(prefix = "spring.datasource"):作用就是将 全局配置文件中
前缀为 spring.datasource的属性值注入到 com.alibaba.druid.pool.DruidDataSource 的同名参数中
*/
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druidDataSource() {
return new DruidDataSource();
}

}

7、在测试类中测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@SpringBootTest
class SpringbootDataJdbcApplicationTests {

//DI注入数据源
@Autowired
DataSource dataSource;

@Test
public void contextLoads() throws SQLException {
//看一下默认数据源
System.out.println(dataSource.getClass());
//获得连接
Connection connection = dataSource.getConnection();
System.out.println(connection);

DruidDataSource druidDataSource = (DruidDataSource) dataSource;
System.out.println("druidDataSource 数据源最大连接数:" + druidDataSource.getMaxActive());
System.out.println("druidDataSource 数据源初始化连接数:" + druidDataSource.getInitialSize());

//关闭连接
connection.close();
}
}

image-20221216171550919

(2)配置Druid数据源监控

继续编写DruidConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//配置 Druid 监控管理后台的Servlet;
//内置 Servlet 容器时没有web.xml文件,所以使用 Spring Boot 的注册 Servlet 方式
@Bean
public ServletRegistrationBean statViewServlet() {
ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");

// 这些参数可以在 com.alibaba.druid.support.http.StatViewServlet
// 的父类 com.alibaba.druid.support.http.ResourceServlet 中找到
Map<String, String> initParams = new HashMap<>();
initParams.put("loginUsername", "admin"); //后台管理界面的登录账号
initParams.put("loginPassword", "123456"); //后台管理界面的登录密码

//后台允许谁可以访问
//initParams.put("allow", "localhost"):表示只有本机可以访问
//initParams.put("allow", ""):为空或者为null时,表示允许所有访问
initParams.put("allow", "");
//deny:Druid 后台拒绝谁访问
//initParams.put("lotus", "192.168.1.20");表示禁止此ip访问

//设置初始化参数
bean.setInitParameters(initParams);
return bean;
}

配置完毕后,我们可以选择访问 :http://localhost:8080/druid/login.html 进入Druid Monitor页面

执行sql语句查询后,也能在该页面进行SQL监控

image-20221216172236332

(3)配置 Druid web 监控 filter 过滤器

可以继续编写DruidConfig,添加过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//配置 Druid 监控 之  web 监控的 filter
//WebStatFilter:用于配置Web和Druid数据源之间的管理关联监控统计
@Bean
public FilterRegistrationBean webStatFilter() {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());

//exclusions:设置哪些请求进行过滤排除掉,从而不进行统计
Map<String, String> initParams = new HashMap<>();
initParams.put("exclusions", "*.js,*.css,/druid/*,/jdbc/*");
bean.setInitParameters(initParams);

//"/*" 表示过滤所有请求
bean.setUrlPatterns(Arrays.asList("/*"));
return bean;
}

3、整合Mybatis

官方文档:http://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/

Maven仓库地址:https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter/2.1.1

1、导入依赖

1
2
3
4
5
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>

2、配置数据库连接信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
spring:
datasource:
username: root
password: "123456"
#?serverTimezone=UTC解决时区的报错
url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT%2B8&useSSL=false&useUnicode=true&characterEncoding=UTF-8&allowPublicKeyRetrieval=true
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource

#Spring Boot 默认是不注入这些属性值的,需要自己绑定
#druid 数据源专有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true

#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
#如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority
#则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

mybatis:
type-aliases-package: com.lotus.pojo
# 这里的classpath为resources目录
mapper-locations: classpath:mybatis/mapper/*.xml
server:
port: 8080

3、创建实体类,导入 Lombok!

User.java

1
2
3
4
5
6
7
8
9
10
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

private Integer id;
private String name;
private String pwd;

}

4、创建mapper目录以及对应的 Mapper 接口

UserMapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 这个注解表示了 这是一个mybatis 的 mapper类  ; Dao层
@Mapper
@Repository
// 由于这是属于dao层 所以配置注解用Repository或者也可以通用的Component
public interface UserMapper {

List<User> queryUserList();

User queryUserById(int id);

int addUser(User user);

int updateUser(User user);

int deleteUser(int id);
}

5、接下来该去配置mapper.xml文件了,这里建议创建在resources的目录下:

UserMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
	<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--configuration core file-->
<mapper namespace="com.lotus.mapper.UserMapper">

<select id="queryUserList" resultType="User">
select * from user
</select>

<select id="queryUserById" resultType="User">
select * from user where id = #{id}
</select>

<select id="addUser" parameterType="User">
insert into user (id,name,pwd) values (#{id},#{name},#{pwd})
</select>

<select id="updateUser" parameterType="User">
update user set name=#{name},pwd=#{pwd} where id = #{id}
</select>

<select id="deleteUser" parameterType="int">
delete from user where id = #{id}
</select>
</mapper>

6、暂时先不写service层,直接写controller调用mapper

UserController.java

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
package com.lotus.controller;

import com.lotus.mapper.UserMapper;
import com.lotus.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class UserController {

@Autowired
private UserMapper userMapper;

@GetMapping("/queryUserList")
public List<User> queryUserList(){
List<User> userList = userMapper.queryUserList();
for (User user :userList){
System.out.println(user);
}
return userList;
}
// 添加一个用户
@GetMapping("/addUser")
public String addUser(){
userMapper.addUser(new User(5,"阿毛","456789"));
return "ok";
}
// 修改一个用户
@GetMapping("/updateUser")
public String updateUser(){
userMapper.updateUser(new User(5,"阿毛","421319"));
return "update ok";
}
// 根据id删除用户
@GetMapping("/deleteUser")
public String deleteUser(){
userMapper.deleteUser(5);
return "delete ok";
}
}

大功告成!

六、Springboot集成Swagger

先来了解一个概念,前后端分离:

  • 前端 -> 前端控制层、视图层
  • 后端 -> 后端控制层、服务层、数据访问层
  • 前后端通过API进行交互
  • 前后端相对独立,且松耦合

这样就会带来一个问题,就是沟通问题.前后端集成。前端或者后端无法做到“及时协商,尽早解决”,最终导致问题集中爆发

解决方案:

  • 首先定义schema [ 计划的提纲 ],并实时跟踪最新的API,降低集成风险
  • 早些年制定word计划文档
  • 前后端分离:前端测试后端接口:postman
    后端提供接口,需要实时更新最新的消息及改动!

Swagger

  • 号称世界上最流行的API框架
  • Restful Api 文档在线自动生成器 => API 文档 与API 定义同步更新
  • 直接运行,在线测试API接口(其实就是controller requsetmapping)
  • 支持多种语言 (如:Java,PHP等)

不多介绍,进入实战

1、Springboot集成Swagger

1、添加Maven依赖

1
2
3
4
5
6
7
8
9
10
11
12
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>

2、编写HelloController,测试确保运行成功!

3、要使用Swagger,我们需要编写一个配置类-SwaggerConfig来配置 Swagger

1
2
3
4
@Configuration //配置类
@EnableSwagger2// 开启Swagger2的自动配置
public class SwaggerConfig {
}

4、访问测试,http://localhost:8080/swagger-ui.html ,可以看到swagger的界面;

image-20221219205545361

(1)、配置Swagger

1、Swagger实例Bean是Docket,所以通过配置Docket实例来配置Swagger,通过Docket对象接管了原来默认的配置

1
2
3
4
@Bean  //配置docket以配置Swagger具体参数
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()); // Docket 实例关联上 apiInfo()
}

2、可以通过apiInfo()属性配置文档信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//配置文档信息
private ApiInfo apiInfo() {
Contact contact = new Contact("Lotus", "https://lotus6.cn", "915491406@qq,com");
return new ApiInfo(
"Lotus的SwaggerAPI文档", // 标题
"学习演示如何配置Swagger", // 描述
"v1.0", // 版本
"https://lotus6.cn", // 组织链接
contact, // 联系人信息
"Apach 2.0 许可", // 许可
"许可链接", // 许可连接
new ArrayList<>()// 扩展
);
}

效果如图

image-20221219210612679

(2)、配置扫描接口

1、构建Docket时通过select()方法配置怎么扫描接口。

1
2
3
4
5
6
7
8
@Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()// 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口
.apis(RequestHandlerSelectors.basePackage("com.lotus.swagger.controller"))
.build();
}

2、重启项目测试,由于我们配置根据包的路径扫描接口,所以我们只能看到一个类

image-20221220003537267

3、除了通过包路径配置扫描接口外,还可以通过配置其他方式扫描接口,这里注释一下所有的配置方式:

1
2
3
4
5
6
7
8
9
any() // 扫描所有,项目中的所有接口都会被扫描到
none() // 不扫描接口
// 通过方法上的注解扫描,如withMethodAnnotation(GetMapping.class)只扫描get请求
withMethodAnnotation(final Class<? extends Annotation> annotation)
// 通过类上的注解扫描,如.withClassAnnotation(Controller.class)只扫描有controller注解的类中的接口
withClassAnnotation(final Class<? extends Annotation> annotation)
basePackage(final String basePackage) // 根据包路径扫描接口
paths(PathSelectors.ant("/lotus/**")) //过滤什么路径:过滤/lotus下的所有路径

4、除此之外,我们还可以配置接口扫描过滤:

1
2
3
4
5
6
7
8
9
10
@Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()// 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口
.apis(RequestHandlerSelectors.basePackage("com.lotus.swagger.controller"))
// 配置如何通过path过滤,即这里只扫描请求以/lotus开头的接口
.paths(PathSelectors.ant("/lotus/**"))
.build();
}

5、这里的可选值有

1
2
3
4
any() // 任何请求都扫描
none() // 任何请求都不扫描
regex(final String pathRegex) // 通过正则表达式控制
ant(final String antPattern) // 通过ant()控制

(3)配置API分组

如果没有配置分组,默认是default。通过groupName()方法即可配置分组:

1
2
3
4
5
6
@Bean
public Docket docket(Environment environment) {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
.groupName("hello") // 配置分组
// 省略配置....
}

置多个分组只需要配置多个docket即可

(4)实体配置

1、新建一个实体类

1
2
3
4
5
6
7
@ApiModel("用户实体")
public class User {
@ApiModelProperty("用户名")
public String username;
@ApiModelProperty("密码")
public String password;
}

2、只要这个实体在请求接口的返回值上(即使是泛型),都能映射到实体项中:

1
2
3
4
@RequestMapping("/getUser")
public User getUser(){
return new User();
}

image-20221224180949236

(5)配置Swagger开关

可以指定相应的环境是否开启swagger功能

1、通过enable()方法配置是否启用swagger,如果是false,swagger将不能在浏览器中访问了

1
2
3
4
5
6
7
8
9
10
11
@Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(false) //配置是否启用Swagger,如果是false,在浏览器将无法访问
.select()// 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口
.apis(RequestHandlerSelectors.basePackage("com.lotus.swagger.controller"))
// 配置如何通过path过滤,即这里只扫描请求以/lotus开头的接口
.paths(PathSelectors.ant("/lotus/**"))
.build();
}

2、如何动态配置当项目处于test、dev环境时显示swagger,处于prod时不显示?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Bean
public Docket docket(Environment environment) {
// 设置要显示swagger的环境
Profiles of = Profiles.of("dev", "test");
// 判断当前是否处于该环境
// 通过 enable() 接收此参数判断是否要显示
boolean flag = environment.acceptsProfiles(of);

return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(flag) //配置是否启用Swagger,如果是false,在浏览器将无法访问
.select()// 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口
.apis(RequestHandlerSelectors.basePackage("com.lotus.swagger.controller"))
// 配置如何通过path过滤,即这里只扫描请求以/lotus开头的接口
.paths(PathSelectors.ant("/lotus/**"))
.build();
}

如此,就能在开发环境中灵活的处理了!

七、任务

1、异步任务

所谓异步,在某些功能实现时可能要花费一定的时间,但是为了不影响客户端的体验,选择异步执行

案例:

首先创建一个service:

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class AsyncService {

public void hello(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("数据正在传输...");
}
}

Controller:

1
2
3
4
5
6
7
8
9
10
11
12
@RestController
public class AsyncController {

@Autowired
AsyncService asyncService;

@RequestMapping("/async")
public String async(){
asyncService.hello();
return "ok";
}
}

这样在执行/async请求时,网站会延时三秒再显示ok,后台数据也会三秒后显示数据正在传输。现在想要做到前端快速响应我们的页面,后台去慢慢的传输数据,就要用到springboot自带的功能

①想办法告诉spring我们的异步方法是异步的,所以要在方法上添加注解

1
2
3
4
5
6
7
8
9
@Async
public void hello(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("数据正在传输...");
}

②去springboot主程序中开启异步注解功能

1
2
3
4
5
6
7
8
9
@EnableAsync
@SpringBootApplication
public class SwaggerDemoApplication {

public static void main(String[] args) {
SpringApplication.run(SwaggerDemoApplication.class, args);
}

}

这样,能实现sleep方法异步执行了。

2、定时任务

项目开发中经常需要执行一些定时任务,比如需要在每天凌晨的时候,分析一次前一天的日志信息,Spring为我们提供了异步执行任务调度的方式,提供了两个接口。

  • TaskExecutor接口
  • TaskScheduler接口

两个注解:

  • @EnableScheduling
  • @Scheduled

Cron表达式:

cron的表达式被用来配置CronTrigger实例。

  cron的表达式是字符串,实际上是由七子表达式,描述个别细节的时间表。

  这些子表达式是分开的空白,代表:

  1. Seconds
  2. Minutes
  3. Hours
  4. Day-of-Month
  5. Month
  6. Day-of-Week
  7. Year (可选字段)

例: “0 0 12 ? * WED” 在每星期三下午12:00 执行,个别子表达式可以包含范围;

例如,在前面的例子里(“WED”)可以替换成 “MON-FRI”, “MON, WED, FRI”甚至”MON-WED,SAT”.“*” 代表整个时间段.

Cron表达式的格式:秒 分 时 日 月 周 年(可选)。

字段名 允许的值 允许的特殊字符
秒 0-59 , - * /
分 0-59 , - * /
小时 0-23 , - * /
日 1-31 , - * ? / L W C
月 1-12 or JAN-DEC , - * /
周几 1-7 or SUN-SAT , - * ? / L C #
年 (可选字段) empty, 1970-2099 , - * /

“?”字符:表示不确定的值

“,”字符:指定数个值

“-”字符:指定一个值的范围

“/”字符:指定一个值的增加幅度。n/m表示从n开始,每次增加m

“L”字符:用在日表示一个月中的最后一天,用在周表示该月最后一个星期X

“W”字符:指定离给定日期最近的工作日(周一到周五)

“#”字符:表示该月第几个周X。6#3表示该月第3个周五

案例:

1、创建一个ScheduledService

1
2
3
4
5
6
7
8
9
10
11
@Service
public class ScheduledService {

//秒 分 时 日 月 周几
//0 * * * * MON-FRI
//注意cron表达式的用法;
@Scheduled(cron = "0 * * * * 0-7")
public void hello(){
System.out.println("hello.....");
}
}

2、这里写完定时任务之后,我们需要在主程序上增加@EnableScheduling 开启定时任务功能

1
2
3
4
5
6
7
8
9
10
@EnableAsync //开启异步注解功能
@EnableScheduling //开启基于注解的定时任务
@SpringBootApplication
public class SpringbootTaskApplication {

public static void main(String[] args) {
SpringApplication.run(SpringbootTaskApplication.class, args);
}

}

3、邮件任务

邮件发送,在我们的日常开发中,也非常的多,Springboot也帮我们做了支持

  • 邮件发送需要引入spring-boot-start-mail
  • SpringBoot 自动配置MailSenderAutoConfiguration
  • 定义MailProperties内容,配置在application.yml中
  • 自动装配JavaMailSender
  • 测试邮件发送

1、引入pom依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>

2、查看自动配置类:MailSenderAutoConfiguration

image-20221224185740166

点进去,看到里面存在bean,JavaMailSenderImpl

image-20221224185915956

看下配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@ConfigurationProperties(
prefix = "spring.mail"
)
public class MailProperties {
private static final Charset DEFAULT_CHARSET;
private String host;
private Integer port;
private String username;
private String password;
private String protocol = "smtp";
private Charset defaultEncoding;
private Map<String, String> properties;
private String jndiName;
}

3、配置文件:

1
2
3
4
5
spring.mail.username=你的邮箱
spring.mail.password=你的邮箱授权码
spring.mail.host=smtp.qq.com
# qq需要配置ssl,其他邮箱不需要
spring.mail.properties.mail.smtp.ssl.enable=true

4、Spring单元测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@SpringBootTest
class SwaggerDemoApplicationTests {

@Autowired
JavaMailSenderImpl mailSender;

@Test
public void contextLoads() {
//邮件设置1:一个简单的邮件
SimpleMailMessage message = new SimpleMailMessage();
message.setSubject("这是一个测试邮件发送标题");
message.setText("这是一个测试邮件发送内容");

message.setTo("915491406@qq.com");
message.setFrom("915491406@qq.com");
mailSender.send(message);
}
}

查看邮箱,邮件接收成功!

我们只需要使用Thymeleaf进行前后端结合即可开发自己网站邮件收发功能了!