SpringBoot

1、创建 SpringBoot 项目

1.1、利用官网创建

  1. 进入 SpringBoot 官网 SpringBoot

  2. 划到 Quickstart Your Project

  3. 点击 Spring Initializr

  4. 如下图

    image-20211020171219051

    Dependencies 处添加项目依赖 最后点击 GENERATE 会自动下载压缩包, 包中即为创建好的 SpringBoot i项目

1.2、利用 IDEA 创建

一般开发都是在 IDEA 中创建

1.3、项目结构

pom.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
<dependencies>
<!--所有的 SpringBoot 依赖都是使用 spring-boot-starter 开头-->
<!-- web 依赖: 自动集成了 Tomcat, dispatcherServlet-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!--单元测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<!--打 jar 包插件-->
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

更改端口号:

application.propertise

1
2
# 更改项目端口号
server.port=8081

更改启动页面

  • resource 下新建 banner.txt 在里面导入自己想要的图案即可

2、SpringBoot 原理初步

自动配置:

pom.xml

  • SpingBoot-dependencies: 核心依赖在父工程中
1
2
3
4
5
6
7
<parent>
<groupId>org.springframework.boot</groupId>
<--核心依赖在父工程中-->
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
  • 在写一些或者引入 SpringBoot 的一些依赖中不需要写版本号, 就是因为有这些版本仓库

启动器

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
  • 启动器即为 SpringBoot 的启动场景

  • 比如 spring-boot-starter-web 其就会导入 web 依赖

  • SpringBoot 会将所有的功能场景 都变为一个一个启动器

  • 需要使用什么功能 就只需要找到对应的启动器就可以了

主程序

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

public static void main(String[] args) {
//将 SpringBoot 应用启动
SpringApplication.run(P1Application.class, args);
}

}

@SpringBootApplication 标注这个类是一个 SpringBoot 的应用 获取启动类的所有配置

  • 注解

    • ```
      @SpringBootConfiguration : SpringBoot 的配置

      @Configuration Spring 配置类
      @Component 这说明也是一个 Spring 的组件
      

      @EnableAutoConfiguration : 自动配置

      @AutoConfigurationPackage 自动配置包
          @Import({Registrar.class}) 导入选择器 包注册
      @Import({AutoConfigurationImportSelector.class}) 自动配置导入选择
      

      //获取所有的配置
      protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes)

      1
      2
      3
      4
      5
      6
      7
      8
      9

      获取候选的配置

      ```java
      protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
      List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
      Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
      return configurations;
      }

      META-INF/spring.factories 自动配置的核心文件

      image-20211020204954252

      1
      2
      如果这里面的条件都满足 才会生效
      @ConditionalOnXXX

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

    1. SpringBoot在启动时 从类路径下 /META-INF/spring.factories 获取指定的值
    2. 将这些自动配置的类导入容器, 自动配置类就会生效 进行自动配置
    3. 以前需要自动配置的东西 现在 SpringBoot 帮用户去做了
    4. 整合 JavaEE 解决方案和自动配置的东西都在 spring-boot-autoconfigure:2.5.5 这个包下
    5. 它会把所有需要导入的组件, 以类名的方式返回, 这些组件就会被添加到容器
    6. 容器中也会存在非常多的 XXX AutoConfiguration 的文件 就是这些类给容器中导入了这个场景需要的所有组件; 并自动配置 @Configuration, JavaConfig
    7. 有了自动配置类 免去了手动编写配置文件的工作

3、主启动类的运行

1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
public class P1Application {

public static void main(String[] args) {
//SpringApplication 类
//run 方法
SpringApplication.run(P1Application.class, args);
}

}

分析该方法主要分为两部分, 一部分是 SpringApplication 的实例化, 二是 run 方法的执行

3.1、SpringApplication

这个类主要做了以下四件事情:

  • 推断应用的类型是普通项目还是 Web 项目
  • 查找并加载所有的可初始化器 设置到 initializers 属性中
  • 找出所有的应用程序监听器 设置到 listeners 属性中
  • 推断并设置 main 方法的定义类 找到运行的主类

查看构造器

1
2
3
4
5
6
7
public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
// ......
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.setInitializers(this.getSpringFactoriesInstances();
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();
}

img

4、SpringBoot 配置

官方不推荐使用 properties 文件 推荐使用 .yaml 文件

4.1、配置文件

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

  • application.properties

    • 语法结构 :key=value
  • application.yml

    • 语法结构 :key:空格 value

配置文件的作用 :修改SpringBoot自动配置的默认值,因为SpringBoot在底层都给我们自动配置好了;

比如我们可以在配置文件中修改Tomcat 默认启动的端口号!测试一下!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
server:
port: 8081

# 普通的 key - value
name: cu1

# 对象
student:
name: cu1
age: 11

# 对象的行内写法
student: {name: Cu1, age: 11}

# 数组
pets:
- cat
- dog
- pig

pets: [cat, dog, pig]

yaml 可以给实体类赋值

1
2
3
//如果不配置使用了这个注解 就会提示报红
//使用该注解来使用 yaml 给实体类赋值
@ConfigurationProperties

image-20211021202656376

实体类

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
@Component
@Data
@NoArgsConstructor
@AllArgsConstructor
//如果不配置使用了这个注解 就会提示报红
@ConfigurationProperties(prefix = "person")
public class Person {

private String name;
private Integer age;
private Boolean happy;
private Date birthday;
private Map<String, Object> maps;
private List<Object>lists;
private Dog dog;
}

//Dog
@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Dog {

@Value("旺财")
private String name;
@Value("3")
private Integer age;

}

yaml 配置

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

注意:

​ 关于上面的报红, 不影响使用. 但是可以点击上面的 Open documentation 来打开帮助文档获取了依赖并添加到pom.xml

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

@ConfigurationPropertie 的作用

  • 将配置文件中配置的每一个属性的值, 映射到这个组件中;
  • 告诉 SpringBoot 将本类中的所有属性和配置文件中相关的配置进行绑定
  • 参数 prefix = "person": 将配置文件中的 person 下面的所有属性一 一对应
  • 只有这个组件是容器中的组件, 才能使用容器提供的 @ConfigurationPropertie 功能

注: 在配置文件中使用 properties 会出现乱码 这里在 setting 里把字体编码设置为 UTF-8

使用 properties 进行配置 但是需要自己手动取出配置文件中的值

resources 下新建 Cu1.properties

1
2
name=Cu1
age=11

实体类中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Component
@Data
@NoArgsConstructor
@AllArgsConstructor
//加载指定的配置文件
@PropertySource(value = "classpath:Cu1.properties")
public class Person {

//SPEL 表达式取出配置文件的值
@Value("${name}")
private String name;
private Integer age;
private Boolean happy;
private Date birthday;
private Map<String, Object> maps;
private List<Object>lists;
private Dog dog;
}

img

  1. @ConfigurationProperties只需要写一次即可 , @Value则需要每个字段都添加

  2. 松散绑定:这个什么意思呢? 比如我的yml中写的last-name,这个和lastName是一样的, - 后面跟着的字母默认是大写的。这就是松散绑定。可以测试一下

  3. JSR303数据校验 , 这个就是我们可以在字段是增加一层过滤器验证 , 可以保证数据的合法性

  4. 复杂类型封装,yml中可以封装对象 , 使用value就不支持

结论:

  • 配置yml和配置properties都可以获取到值 , 强烈推荐 yml;

  • 如果我们在某个业务中,只需要获取配置文件中的某个值,可以使用一下 @value;

  • 如果说,我们专门编写了一个JavaBean来和配置文件进行一一映射,就直接@configurationProperties,不要犹豫!

4.2、JSR303数据校验

先看看如何使用

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

1
2
3
4
5
6
7
8
@Component //注册bean
@ConfigurationProperties(prefix = "person")
@Validated //数据校验
public class Person {

@Email(message="邮箱格式错误") //name必须是邮箱格式
private String name;
}

运行结果 :default message [不是一个合法的电子邮件地址];

img

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

常见参数
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.3、多环境切换

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

4.3.1、配置文件

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

例如:

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

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

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

我们需要通过一个配置来选择需要激活的环境:

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

4.3.2、yaml的多文档块

和properties配置文件中一样,但是使用yml去实现不需要创建多个配置文件,更加方便了 !

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

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


---

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

注意:如果yml和properties同时都配置了端口,并且没有激活其他环境 , 默认会使用properties配置文件的!

properties 文件优先级最低会导致配置覆盖

4.3.3、配置文件加载位置

外部加载配置文件的方式十分多,我们选择最常用的即可,在开发的资源文件中进行配置!

官方外部配置文件说明参考文档

img

springboot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件:

1
2
3
4
优先级1:项目路径下的config文件夹配置文件
优先级2:项目路径下配置文件
优先级3:资源路径下的config文件夹配置文件
优先级4:资源路径下配置文件

优先级由高到底,高优先级的配置会覆盖低优先级的配置;

SpringBoot会从这四个位置全部加载主配置文件;互补配置;

我们在最低级的配置文件中设置一个项目访问路径的配置来测试互补问题;

4.3.4、拓展,运维小技巧

指定位置加载配置文件

我们还可以通过spring.config.location来改变默认的配置文件位置

项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;这种情况,一般是后期运维做的多,相同配置,外部指定的配置文件优先级最高

1
java -jar spring-boot-config.jar --spring.config.location=F:/application.properties

4.4、自动装配原理深入

4.4.1、分析自动配置原理

我们以 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
46
47
//表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件;
@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;
//如果不存在,判断也是成立的
//即使我们配置文件中不配置pring.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类中获取的,这些类里面的每一个属性又是和配置文件绑定的;

  • 所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;

  • 配置文件能配置什么就可以参照某个功能对应的这个属性类

    我们去配置文件里面试试前缀,看提示!

img

这就是自动装配的原理!

4.4.2、精髓

1、SpringBoot启动会加载大量的自动配置类

2、我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;

3、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)

4、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;

xxxxAutoConfigurartion:自动配置类;给容器中添加组件

xxxxProperties:封装配置文件中相关属性;

4.4.3、了解:@Conditional

了解完自动装配的原理后,我们来关注一个细节问题,自动配置类必须在一定的条件下才能生效;

@Conditional派生注解(Spring注解版原生的@Conditional作用)

作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;

img

那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了。

我们怎么知道哪些自动配置类生效?

我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;

1
2
#开启springboot的调试类
debug=true

Positive matches:(自动配置类启用的:正匹配)

Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)

Unconditional classes: (没有条件的类)

5、SpringBoot Web 开发

要解决的问题:

  • 导入静态资源
  • 首页
  • jsp 模板引擎 Thymeleaf
  • 装配和扩展 SpringMVC
  • 增删改查
  • 拦截器
  • 国际化

5.1、静态资源导入

5.1.1、静态资源映射规则

首先,我们搭建一个普通的SpringBoot项目,回顾一下HelloWorld程序!

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

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

我们先来聊聊这个静态资源映射规则:

SpringBoot中,SpringMVC的web配置都在 WebMvcAutoConfiguration 这个配置类里面;

我们可以去看看 WebMvcAutoConfigurationAdapter 中有很多配置方法;

有一个方法: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

Webjars 本质就是以jar包的方式引入我们的静态资源 , 我们以前要导入一个静态资源文件,直接导入即可。

使用 SpringBoot 需要使用 Webjars,我们可以去搜索一下:

网站:https://www.webjars.org

要使用jQuery,我们只要要引入jQuery对应版本的pom依赖即可!

1
<dependency>    <groupId>org.webjars</groupId>    <artifactId>jquery</artifactId>    <version>3.4.1</version></dependency>

导入完毕,查看webjars目录结构,并访问Jquery.js文件!

img

访问:只要是静态资源,SpringBoot 就会去对应的路径寻找资源,我们这里访问:http://localhost:8080/webjars/jquery/3.4.1/jquery.js

5.1.2、第二种静态资源映射规则

项目中使用自己的静态资源该怎么导入;

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

1
// 进入方法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/" };

ResourceProperties 可以设置和我们静态资源有关的参数;这里面指向了它会去寻找资源的文件夹,即上面数组的内容。

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

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

我们可以在 resources 根目录下新建对应的文件夹,都可以存放我们的静态文件;

比如我们访问http://localhost:8080/1.js, 他就会去这些文件夹中寻找对应的静态资源文件;

5.1.3、自定义静态资源路径

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

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

一旦自己定义了静态文件夹的路径,原来的自动配置就都会失效了!

5.1.4、总结:

  • 参考源码推测出, 如果使用了自定义的配置文件并指定其为静态资源文件目录 那么默认的静态资源目录就会失效

  • 在利用 url 访问静态资源目录时 首先判断是不是访问 /webjars/** 如果是, 就会将 url 拼接到 classpath:/META-INF/resources/webjars/ 下的静态资源目录下; 否则就会直接去按照第二种静态资源目录的规则去寻找

  • 第二种静态资源目录的优先级

    resources > static(默认) > public

5.2、首页和图标定制

5.2.1、首页处理

继续向下看源码! 可以看到一个欢迎页的映射, 就是我们的首页!

1
@Beanpublic WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,                                                           FormattingConversionService mvcConversionService,                                                           ResourceUrlProvider mvcResourceUrlProvider) {    WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(  new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(), // getWelcomePage 获得欢迎页        this.mvcProperties.getStaticPathPattern());    welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));    return welcomePageHandlerMapping;}

点进去继续看 getWelcomPage()

1
2
3
4
5
6
7
8
9
10
11
12
private Optional<Resource> getWelcomePage() {
String[] locations = getResourceLocations(this.resourceProperties.getStaticLocations());
// ::是java8 中新引入的运算符
// Class::function的时候function是属于Class的,应该是静态方法。
// this::function的funtion是属于这个对象的。
// 简而言之,就是一种语法糖而已,是一种简写
return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();
}
// 欢迎页就是一个location下的的 index.html 而已
private Resource getIndexHtml(String location) {
return this.resourceLoader.getResource(location + "index.html");
}

对于设置欢迎页, 可以放在静态资源文件夹下的 index.html 文件将会被 /** 映射 一般都放在 resouces/static 文件夹下

比如访问 http://localhost:8080/ ,就会找静态资源文件夹下的 index.html

新建一个index.html,在我们上面的3个目录中任意一个;然后访问测试 http://localhost:8080/ 看结果!

对于网站图标 如果使用的是 SpringBoot2.x 之后的版本 设置关闭默认图标就失效了

5.2.2、图标定制

后来在网上搜怎么自定义图标 找到了一篇博客 但是没有生效在这里将博客链接贴一下, 有兴趣可以去尝试一下,

这个并不重要 关于 SpringBoot2.x之后版本的自定义图标

5.3、模板引擎 Thymeleaf

5.3.1、模板引擎

​ 前端交给我们的页面,是 html 页面。如果是我们以前开发,我们需要把他们转成 jsp 页面,jsp好处就是当我们查出一些数据转发到 JSP 页面以后,我们可以用jsp轻松实现数据的显示,及交互等。

jsp 支持非常强大的功能,包括能写 Java 代码,但是呢,我们现在的这种情况,SpringBoot 这个项目首先是以 jar 的方式,不是 war,像第二,我们用的还是嵌入式的 Tomcat,所以呢,他现在默认是不支持jsp的

那不支持 jsp ,如果我们直接用纯静态页面的方式,那给我们开发会带来非常大的麻烦,那怎么办呢?

SpringBoot 推荐你可以来使用模板引擎:

模板引擎,我们其实大家听到很多,其实jsp就是一个模板引擎,还有用的比较多的 freemarker ,包括SpringBoot 给我们推荐的 Thymeleaf,模板引擎有非常多,但再多的模板引擎,他们的思想都是一样的,什么样一个思想呢我们来看一下这张图:

img

模板引擎的作用就是我们来写一个页面模板,比如有些值呢,是动态的,我们写一些表达式。而这些值,从哪来呢,就是我们在后台封装一些数据。然后把这个模板和这个数据交给我们模板引擎,模板引擎按照我们这个数据帮你把这表达式解析、填充到我们指定的位置,然后把这个数据最终生成一个我们想要的内容给我们写出去,这就是我们这个模板引擎,不管是jsp还是其他模板引擎,都是这个思想。只不过呢,就是说不同模板引擎之间,他们可能这个语法有点不一样。其他的我就不介绍了,我主要来介绍一下 SpringBoot 给我们推荐的 Thymeleaf 模板引擎,这模板引擎呢,是一个高级语言的模板引擎,他的这个语法更简单。而且呢,功能更强大。

5.3.2、引入Thymeleaf

怎么引入呢,对于springboot来说,什么事情不都是一个start的事情嘛,我们去在项目中引入一下。给大家三个网址:

Thymeleaf 官网

Thymeleaf 在Github 的主页

Spring官方文档:找到对应的版本

导入 pom 依赖

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

5.3.4、Thymeleaf分析

前面呢,我们已经引入了 Thymeleaf,那这个要怎么使用呢?

我们首先得按照 SpringBoot 的自动配置原理看一下我们这个Thymeleaf的自动配置规则,在按照那个规则,我们进行使用。

我们去找一下 Thymeleaf 的自动配置类:ThymeleafProperties

1
2
3
4
5
6
7
8
9
10
11
12
@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 什么都不需要配置,只需要将其放在指定的文件夹下即可!

5.3.5、测试

test.html

1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title></head>
<body>test<!--所有的 html 元素都可以被 thymeleaf 替换接管 th:元素名-->
<div th:text="${msg}"></div>
</body>
</html>

Controller

1
2
3
4
5
6
7
8
9
10
//在 template 目录下的所有页面 只能通过 Controller 来跳转
// 这个需要模板引擎支持
@Controllerpublic
class IndexController {
@RequestMapping("/test")
public String test(Model model) {
model.addAttribute("msg", "helloSpringBoot");
return "test";
}
}

5.3.6、Thymeleaf 语法

学习语法, 最好是参考官方文档: Thymeleaf 官网

  1. 修改测试请求,增加数据传输;
1
2
3
4
5
6
7
@RequestMapping("/t1")
public String test1(Model model) {
//存入数据
model.addAttribute("msg", "Hello,Thymeleaf");
//classpath:/templates/test.html
return "test";
}
  1. 要使用thymeleaf,需要在html文件中导入命名空间的约束,方便提示;

    去官方文档的#3中看一下命名空间拿来过来:

1
xmlns:th="http://www.thymeleaf.org"
  1. 编写下前端页面
1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title></head>
<body>test<!--所有的 html 元素都可以被 thymeleaf 替换接管 th: 元素名-->
<div th:text="${msg}"></div>
<h3 th:each="user:${users}" th:text="${user}"></h3></body>
</html>

对于语法的深入

1. 可以使用任意的 th:attr 来替换Html中原生属性的值!

img

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
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: _

测试 循环取出数组中内容

Controller

1
2
3
4
5
6
7
8
9
@Controllerpublic
class IndexController {
@RequestMapping("/test")
public String test(Model model) {
model.addAttribute("msg", "helloSpringBoot");
model.addAttribute("users", Arrays.asList("Cu1", "Cu2"));
return "test";
}
}

html

1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title></head>
<body>test<!--所有的 html 元素都可以被 thymeleaf 替换接管 th: 元素名-->
<div th:text="${msg}"></div>
<h3 th:each="user:${users}" th:text="${user}"></h3></body>
</html>

5.4、SpringMVC 配置

5.4.1、MVC自动配置原理
官网阅读

在进行项目编写前,还需要知道一个东西,就是 SpringBootSpringMVC 还做了哪些配置,包括如何扩展,如何定制.

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

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 内容协商视图解析器

==注: 如果想要看更透彻的源码 需要降低 SpringBoot 的版本==

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

即根据方法的返回值取得视图对象 (View),然后由视图对象决定如何渲染(转发, 重定向). 进入内部看这里的源码:可以找到 WebMvcAutoConfiguration , 然后搜索 ContentNegotiatingViewResolver。找到如下方法!

1
2
3
4
5
6
7
8
9
@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;
}

进入 resolver 类可以找到对应解析视图的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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;
}
} // .....
}

可以看到在 resolveViewName 方法中首先判断是否有用户自己定义的视图对象, 如果有就调用 getBestView 方法去调用getCandidateViews 得到 List<> 中获取的候选视图进行选择, 得到一个 bestView

继续点进去看 getCandidateViews 是怎么获得候选的视图的呢

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
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
2
3
4
5
6
7
8
9
10
11
12
13
@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;
}
}

这里更推荐将静态内部类拆分为一个独立的类 然后再单独写一个配置类用来返回自定义的配置类

  • 怎么看自定义实现的视图解析器有没有起作用呢?

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

img

  • 启动项目,随后随便访问一个页面,看一下 Debug 信息, 找到 this

找到视图解析器,看到自定义的视图解析器的就在这里了;

img

转换器和格式化器

WebMvcAutoConfiguration 中找到格式化转换器:

1
2
3
4
5
6
7
8
9
10
@Bean
public FormattingConversionService mvcConversionService() {
Format format = this.mvcProperties.getFormat();
// 拿到配置文件中的格式化规则
WebConversionService conversionService = new WebConversionService((new DateTimeFormatters()).
dateFormat(format.getDate()).timeFormat(format.getTime()).
dateTimeFormat(format.getDateTime()));
this.addFormatters(conversionService);
return conversionService;
}

点击进 format.getXXX() 中如果是新版本就会发现里面有默认属性但是具体是什么并不知道, 猜测是封装起来, 并通过 绑定配置文件在容器中进行初始化

如果是老版本就会有下面的代码

1
2
3
4
5
6
public String getDateFormat() {
return this.dateFormat;
}

/*** Date format to use. For instance, `dd/MM/yyyy`. 默认的 */
private String dateFormat;

可以看到在 Propertiesyaml 文件中,可以自动配置它!

如果配置了自定义的格式化方式,就会注册到 Bean 中生效, 就可以在配置文件中配置日期格式化的规则:

1
2
3
4
spring:
mvc:
format:
date-time: yyyy-MM-dd HH:mm:ss
1
2
3
public void setDate(String date) {
this.date = date;
}

其余的就不一一举例, 可以自行去了解

修改SpringBoot的默认配置

这么多的自动配置,原理都是一样的,通过 WebMVC 的自动配置原理分析,需要学会一种学习方式,通过源码探究,得出结论;这个结论一定是属于自己的,而且一通百通。

SpringBoot 的底层,大量用到了这些设计细节思想,所以,需要多阅读源码! 得出结论;

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

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

扩展使用SpringMVC

官方文档如下:

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.

用户要做的就是编写一个 @Configuration 注解类,并且类型要为 WebMvcConfigurer ,并且不能标注@EnableWebMvc 注解;

下面去自定义一个;新建一个包叫config,写一个类 MyMvcConfig

image-20211024165818485

1
2
3
4
5
6
7
8
9
10
11
12
13
//全面扩展 springMVC
// 如果要扩展 SpringMVC 官方建议这样去做
@Configuration
//@EnableWebMvc
// 其导入了一个类 DelegatingWebMvcConfiguration 从容器中获取所有的 webMvcConfig:
public class MyMvcConfig implements WebMvcConfigurer {
public interface ViewResolver //实现了视图解析器接口的类 就可以把它看作视图解析器
// 视图跳转
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/Cu1").setViewName("test");
}
}

测试请自行启动 SpringBoot 项目进行测试 最后发现自定义视图解析器中访问 /Cu1 后跳转到 test.html

所以说,要扩展SpringMVC,官方就推荐这么去使用,既保SpringBoot留所有的自动配置,也能使用用户扩展的配置!

可以去分析一下原理:

  1. WebMvcAutoConfiguration SpringMVC 的自动配置类,里面有一个类WebMvcAutoConfigurationAdapter

  2. 这个类上有一个注解,在做其他自动配置时会导入:@Import(EnableWebMvcConfiguration.class)

  3. 点进 EnableWebMvcConfiguration 这个类,它继承了一个父类:DelegatingWebMvcConfiguration , 这个父类中有这样一段代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();

    // 从容器中获取所有的webmvcConfigurer
    @Autowired(required = false)
    public void setConfigurers(List<WebMvcConfigurer> configurers) {
    if (!CollectionUtils.isEmpty(configurers)) {
    this.configurers.addWebMvcConfigurers(configurers);
    }
    }
    }
  4. 可以在这个类中去寻找一个刚才设置的 viewController 当做参考,发现它调用了一个

    1
    protected void addViewControllers(ViewControllerRegistry registry) {    this.configurers.addViewControllers(registry);}
  5. 点击进入 addViewControllers 方法

    1
    2
    3
    4
    5
    6
    7
    8
    public void addViewControllers(ViewControllerRegistry registry) {
    Iterator var2 = this.delegates.iterator();
    while (var2.hasNext()) {
    // 将所有的WebMvcConfigurer相关配置来一起调用!包括我们自己配置的和Spring给我们配置的
    WebMvcConfigurer delegate = (WebMvcConfigurer) var2.next();
    delegate.addViewControllers(registry);
    }
    }

    所以得出结论:所有的WebMvcConfiguration都会被作用,不止Spring自己的配置类,用户自己的配置类当然也会被调用;

全面接管SpringMVC

官方文档:

If you want to take complete control of Spring MVC
you can add your own @Configuration annotated with @EnableWebMvc.

全面接管即:SpringBoot对SpringMVC 的自动配置不需要了,所有都是用户自己去配置!

只需在我们的配置类中要加一个 @EnableWebMvc

如果用户选择全面接管了 SpringMVC 了,那么之前 SpringBoot 默认配置的静态资源映射一定会无效,我们可以去测试一下;

当然,在开发中,不推荐使用全面接管SpringMVC

思考问题?为什么加了一个注解,自动配置就失效了!继续看下源码:

  1. 进入@EnableWebMvc 发现它是导入了一个类,可以继续进去看

    1
    @Import({DelegatingWebMvcConfiguration.class})public @interface EnableWebMvc {}
  2. 进入 DelegatingWebMvcConfiguration 类 发现它继承了一个父类 WebMvcConfigurationSupport

    1
    public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {  // ......}
  3. 现在来回顾一下Webmvc自动配置类

    1
    2
    3
    4
    5
    6
    7
    8
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnWebApplication(type = Type.SERVLET)
    @ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
    // 这个注解的意思就是:容器中没有这个组件的时候,这个自动配置类才生效
    @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
    @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
    @AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class})
    public class WebMvcAutoConfiguration { }
  4. 总结一句话:@EnableWebMvcWebMvcConfigurationSupport 组件导入进来了, 导致了 SpringMVC 的配置失效

    而导入的 WebMvcConfigurationSupport 只是 SpringMVC 最基本的功能!

    ==在SpringBoot中会有非常多的扩展配置,只要看见了这个,就应该多留心注意~==

6、数据库

6.1、SpringData简介

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

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

Sping Data 官网

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

6.2、整合JDBC (建议快速看过即可)

创建测试项目测试数据源

  1. 新建一个项目测试:springboot-data-jdbc ; 引入相应的模块!基础模块

    img

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

    1
    2
    3
    4
    5
    6
    7
    spring:
    datasource:
    username: root
    password: 123456
    #?serverTimezone=UTC解决时区的报错(如果不是 mysql 8.0+ 的话不需要设置时区)
    url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver

配置完后,就可以直接去使用了,因为 SpringBoot 已经默认进行了自动配置

测试类测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@SpringBootTestclass
SpringBoot04DataApplicationTests {
@Autowired
DataSource dataSource;
@Test
void contextLoads () throws SQLException {
//查看默认数据源
System.out.println(dataSource.getClass());
//获取数据源class com.zaxxer.hikari.HikariDataSource 默认的数据源
Connection connection = dataSource.getConnection();
// xxxx Template: springBoot 已经配置好的模板 Bean 拿来即用
// 关闭连接
connection.close();
}
}

结果:可以看到默认配置的数据源为 : class com.zaxxer.hikari.HikariDataSource , 并不需要手动配置

进行全局搜索, 找到数据源的所有自动配置都在: DataSourceAutoConfiguration 文件

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

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

SpringBoot 也提供了数据库连接的类 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方法:用于执行存储过程、函数相关语句。

测试:

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
@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 ('狂神说','24736743@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] = "秦疆";
objects[1] = "24736743@sina.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";
}
}

6.3、 集成Druid

Druid简介

Java 程序很大一部分要操作数据库,为了提高性能操作数据库的时候,又不得不使用数据库连接池。

Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0、DBCPDB 池的优点,同时加入了日志监控

Druid 监控 DB 池连接和 SQL 的执行情况,是针对监控而生的 DB 连接池。

Spring Boot 2.0 以上默认使用 Hikari 数据源,可以说 HikariDriud 都是当前Java Web上最优秀的数据源,讲来重点介绍 Spring Boot 如何集成 Druid数据源,实现数据库监控。Github地址

com.alibaba.druid.pool.DruidDataSource 基本配置参数如下:

img

img

img

配置数据源

1
2
3
4
5
6
7
<!--druid 数据源-->
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.6</version>
</dependency>
  • 切换数据源 Spring Boot 2.0 以上默认使用 com.zaxxer.hikari.HikariDataSource 数据源,但可以 通过 spring.datasource.type 指定数据源

  • 下面是 Druid 的配置

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

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
spring:
datasource:
username:
password:
url: jdbc:mysql://47.100.227.175:3306/School?useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.jdbc.Driver
# 切换为 druid 数据源
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

导入Log4j 的依赖

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

进行了上面的配置之后, 就不再使用 SprinhBoot 提供的默认 Druid 数据源了, 而是想要 SpringBoot 使用自定义配置的数据源, 需要自行利用配置类将数据源 DruidDataSource 组件添加到容器中并绑定属性

1
2
3
4
5
6
7
8
9
@Configuration
public class DruidConfig {
//使用 Druid 数据源
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druidDataSource() {
return new DruidDataSource();
}
}

配置Druid数据源监控

Druid 数据源具有监控的功能,并提供了一个 web 界面方便用户查看,类似安装 路由器 时,同时也提供了一个默认的 web 页面。

所以第一步需要设置 Druid 的后台管理页面,比如 登录账号、密码 等;配置后台管理;

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
//后台监控 相当于 web.xml
/*
* 因为 SpringBoot 内置了 servlet 容器 所以没有 web.xml 替代方法 ServletRegistrationBean
* */
@Bean
public ServletRegistrationBean StatViewServlet() {
ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
HashMap<String, String> initParameters = new HashMap<>();
//添加配置
//登录的 key 是固定的
initParameters.put("loginUsername", "admin");
initParameters.put("loginPassword", "35157210");

/*
* 允许谁可以访问
* 若为空 则所有人都可以访问
* 可以填具体的人
* */
initParameters.put("allow", "");

/*
* 禁止谁可以访问
* */
//initParameters.put("用户名", "用户 Ip");
//后台有人需要登录 账号密码配置
bean.setInitParameters(initParameters); //初始化参数
return bean;
}

//filter
@Bean
public FilterRegistrationBean webStarFilter() {
FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();
bean.setFilter(new WebStatFilter());

//可以过滤哪些请求
HashMap<String, String> initParameters = new HashMap<>();

//这些东西不进行统计
initParameters.put("exclusions", "*.js, *.css, /druid/*");

bean.setInitParameters(initParameters);

return bean;
}

配置完毕后,我们可以选择访问 :http://localhost:8080/druid/login.html

image-20211031201739958

进入之后

image-20211031201813993

配置 Druid web 监控 filter 过滤器

1
//filter    @Bean    public FilterRegistrationBean webStarFilter() {        FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();        bean.setFilter(new WebStatFilter());        //可以过滤哪些请求        HashMap<String, String> initParameters = new HashMap<>();        //这些东西不进行统计        initParameters.put("exclusions", "*.js, *.css, /druid/*");        bean.setInitParameters(initParameters);        return bean;    }

平时在工作中,按需求进行配置即可,主要用作监控!

完整代码

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
@Configurationpublic
class DruidConfig {
//使用 Druid 数据源
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druidDataSource() {
return new DruidDataSource();
}

//后台监控 相当于 web.xml
/**
* 因为 SpringBoot 内置了 servlet 容器 所以没有 web.xml 替代方法 ServletRegistrationBean
* */
@Bean
public ServletRegistrationBean StatViewServlet() {
ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
HashMap<String, String> initParameters = new HashMap<>();
//添加配置
// 登录的 key 是固定的
initParameters.put("loginUsername", "admin");
initParameters.put("loginPassword", "35157210");
/** 允许谁可以访问
* 若为空 则所有人都可以访问 * 可以填具体的人
**/
initParameters.put("allow", "");
/**
* 禁止谁可以访问
**/
initParameters.put("用户名", "用户 Ip");
//后台有人需要登录 账号密码配置
bean.setInitParameters(initParameters);
//初始化参数
return bean;
}

//filter
@Bean
public FilterRegistrationBean webStarFilter() {
FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();
bean.setFilter(new WebStatFilter());
//可以过滤哪些请求
HashMap<String, String> initParameters = new HashMap<>();
//这些东西不进行统计
initParameters.put("exclusions", "*.js, *.css, /druid/*");
bean.setInitParameters(initParameters);
return bean;
}
}

6.4、整合 MyBatis

官方文档

Maven仓库地址

整合测试

1
2
3
4
5
6
<!--mybatis 整合 mybatis-spring-boot-starter 非 SpringBoot 官方-->
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.0</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
# 47.100.227.175
spring:
datasource:
username:
password:
url:
jdbc: mysql://47.100.227.175:3306/School?useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.jdbc.Driver # 配置 druid 数据源
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,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500# 配置
MyBatismybatis:
mapper-locations: classpath:mybatis/mapper/*.xml
# type-aliases-package: com.cu1.pojo 扫描包

测试数据库是否连接成功!

创建实体类,导入 Lombok

1
2
3
4
5
6
7
@Data
@AllArgsConstructor
@NoArgsConstructorpublic
class User {
private int id;
private String name;
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//这个注解表示了这是一个 MyBatis 的 Mapper 类
@Mapper
@Repository//这个不需要 只需要 mapper 就可以
public interface UserMapper {

List<User> queryUserList();

User queryUserById(int id);

int addUser(User user);

int updateUser(User user);

int deleteUser(int id);
}

对应的Mapper映射文件

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
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cu1.mapper.UserMapper">

<select id="queryUserList" resultType="com.cu1.pojo.User" useCache="true">
select * from School.User;
</select>

<select id="queryUserById" resultType="com.cu1.pojo.User">
select * from School.User where id = #{id};
</select>

<insert id="addUser" parameterType="com.cu1.pojo.User">
insert into School.User (id, name) values (#{id}, #{name});
</insert>

<update id="updateUser" parameterType="com.cu1.pojo.User">
update School.User set name = #{name} where id = #{id};
</update>

<delete id="deleteUser" parameterType="int">
delete from School.User where id = #{id};
</delete>


</mapper>

**然后设置 mapper.xml 的路径 **

1
2
3
# 配置 MyBatis
mybatis: mapper-locations: classpath:mybatis/mapper/*.xml
# type-aliases-package: com.cu1.pojo 扫描包

这样就把 .xml 配置文件加入到 MyBatis

如果不想配置可以选择在

image-20211101220446773

中加入 MyBatis 扫描包的注解 但是建议把配置文件放到 resources 中统一配置

1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
//扫描某个包下的 mapper 类似于之前的扫描包
//@MapperScan("com.cu1.mapper")
public class SpringBoot05MyBatisApplication {

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

}

编写 Controller 进行测试!

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

@Autowired
private UserMapper userMapper;

@GetMapping("/queryUserList")
public List<User> queryUserList() {
List<User> users = userMapper.queryUserList();
return users;
}

}

7、SpringSecurity

安全简介

  • 在 Web 开发中,安全一直是非常重要的一个方面。安全虽然属于应用的非功能性需求,但是应该在应用开发的初期就考虑进来。如果在应用开发的后期才考虑安全的问题,就可能陷入一个两难的境地:一方面,应用存在严重的安全漏洞,无法满足用户的要求,并可能造成用户的隐私数据被攻击者窃取;另一方面,应用的基本架构已经确定,要修复安全漏洞,可能需要对系统的架构做出比较重大的调整,因而需要更多的开发时间,影响应用的发布进程。因此,从应用开发的第一天就应该把安全相关的因素考虑进来,并在整个应用的开发过程中。

  • 存在比较有名的:ShiroSpring Security

  • 这里需要阐述一下的是,每一个框架的出现都是为了解决某一问题而产生了,那么 Spring Security 框架的出现是为了解决什么问题呢?

  • Spring Security官网地址

Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications.

Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirement

Spring Security 是一个功能强大且高度可定制的身份验证和访问控制框架。它实际上是保护基于 spring的应用程序的标准。

Spring Security 是一个框架,侧重于为 Java 应用程序提供身份验证和授权。与所有 Spring 项目一样,Spring 安全性的真正强大之处在于它可以轻松地扩展以满足定制需求

  • 从官网的介绍中可以知道这是一个 权限框架 想我们之前做项目是没有使用框架是怎么控制权限的?对于权限 一般会细分为 ==功能权限,访问权限,和菜单权限==。代码会写的非常的繁琐,冗余。

  • 怎么解决之前写权限代码繁琐,冗余的问题,一些主流框架就应运而生而 Spring Scecurity 就是其中的一种。

  • Spring 是一个非常流行和成功的 Java 应用开发框架。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。一般来说,Web 应用的安全性包括==用户认证====Authentication==)和==用户授权==(==Authorization==)两个部分。用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。

  • 对于上面提到的两种应用情景,Spring Security 框架都有很好的支持。在用户认证方面,Spring Security框架支持主流的认证方式,包括 HTTP基本认证HTTP 表单验证HTTP 摘要认证OpenIDLDAP 等。在用户授权方面,Spring Security 提供了基于角色的访问控制和访问控制列表(Access Control ListACL),可以对应用中的领域对象进行细粒度的控制。

导入 SpringSecurity

pom.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
28
29
30
31
32
33
34
35
36
37
<dependencies>

<!--Thymeleaf 与 security 整合包-->
<!-- https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity5 -->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>


<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

这里注意一下 thymeleafthymeleaf-extras-springsecurity 的版本号问题

认识SpringSecurity

  1. Spring Security 是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,对于安全控制,我们仅需要引入 spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理!
  2. 核心的几个类:
    • WebSecurityConfigurerAdapter自定义Security策略
  • AuthenticationManagerBuilder:自定义认证策略
  • @EnableWebSecurity:开启WebSecurity模式 【@Enablexxx开启某个功能】
  1. Spring Security的两个主要目标是 “认证” 和 “授权”(访问控制)。

    “认证”(Authentication)

    身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。

    身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。

    “授权” (Authorization)

    授权发生在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的完全权限。

    这个概念是通用的,而不是只在Spring Security 中存在。

编写 Spring Security 配置类

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
//AOP 拦截器
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

//授权
@Override
protected void configure(HttpSecurity http) throws Exception {
//首页所有人都可以访问 功能页只能有对应权限的人才能访问

http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");

//没有权限会默认到登录界面 需要开启登录的页面
// .loginPage("") 定制登录页 参数为跳转登录页的请求
http.formLogin().loginPage("/toLogin").usernameParameter("username").passwordParameter("password").loginProcessingUrl("/login");

//关闭防攻击
http.csrf().disable();

//注销 开启了注销功能 跳到首页
http.logout().logoutSuccessUrl("/");

//开启 '记住我' 功能 cookie 默认保存两周
http.rememberMe().rememberMeParameter("remmeber");
}

//认证 SpringBoot 2.1.x 可以直接使用
//密码编码: PasswordEncoder
//在 Spring Security 5.0+ 新增了很多加密方法
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//从数据库中认证
//auth.jdbcAuthentication();
//从内存中认证
//这些数据正常应该在数据库读
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("Cu1").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2", "vip3")
.and()
.withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1", "vip2", "vip3")
.and()
.withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1");
}
}

**下面将上面的代码拆分解释一下: **

1
2
@EnableWebSecuritypublic 
class SecurityConfig extends WebSecurityConfigurerAdapter

@EnableWebSecurity 配合 继承 WebSecurityConfigurerAdapter 就可以达到开启 Spring Security 的自定义配置

部分自定义配置功能以重写 WebSecurityConfigurerAdapter 中的方法来实现

  1. protected void configure(HttpSecurity http) throws Exception 用来配置用户访问 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
    //授权
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    //首页所有人都可以访问 功能页只能有对应权限的人才能访问

    http.authorizeRequests()
    .antMatchers("/").permitAll()
    .antMatchers("/level1/**").hasRole("vip1")
    .antMatchers("/level2/**").hasRole("vip2")
    .antMatchers("/level3/**").hasRole("vip3");

    //没有权限会默认到登录界面 需要开启登录的页面
    // .loginPage("") 定制登录页 参数为跳转登录页的请求
    http.formLogin().loginPage("/toLogin").usernameParameter("username").passwordParameter("password").loginProcessingUrl("/login");

    //关闭防攻击
    http.csrf().disable();

    //注销 开启了注销功能 跳到首页
    http.logout().logoutSuccessUrl("/");

    //开启 '记住我' 功能 cookie 默认保存两周
    http.rememberMe().rememberMeParameter("remmeber");
    }

    关于 loginProcessingUrl("/login")

    1
    http.formLogin().loginPage("/toLogin").usernameParameter("username").passwordParameter("password").loginProcessingUrl("/login");

    loginProcessingUrl("/login") 表示登录页提交信息的时候是以 “/login”URLSpring Security 并由登录界面 <form th:action="@{/login}" method="post"> 以 之前设定好的 URL 向后台提交数据来由 Spring Security 实现的权限验证功能进行验证 但是在验证成功后有下面两种结果:

    • 通过主动点击登录 (指进入没有权限的页面导致跳转至登录界面) 进行登陆时, 点击登录并由后台验证通过后将自动回到 当前界面
    • 进入没有权限的页面导致跳转至登录界面登录成功且权限符合当前页面的权限后会进入该页面

    写完发现好像登录成功后不论之前进入的是有没有权限的页面 都会自动回到进入之前的页面….

    1. protected void configure(AuthenticationManagerBuilder auth) throws Exception 记录合法的登录信息 用来在后台进行匹配

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      //认证 SpringBoot 2.1.x 可以直接使用
      //密码编码: PasswordEncoder
      //在 Spring Security 5.0+ 新增了很多加密方法
      @Override
      protected void configure(AuthenticationManagerBuilder auth) throws Exception {
      //从数据库中认证
      //auth.jdbcAuthentication();
      //从内存中认证
      //这些数据正常应该在数据库读
      auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
      .withUser("Cu1").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2", "vip3")
      .and()
      .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1", "vip2", "vip3")
      .and()
      .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1");
      }

      现在有一个需求: 用户没有登录的时候, 导航栏上只显示登录按钮, 用户登录之后, 导航栏可以显示登录的用户信息及注销按钮. 以及下面的功能: 比如 Cu1 这个用户, 其只有 vip2, vip3 功能, 那么登录则只显示这两个功能, 而 vip1 的功能菜单不显示.

      实现上面的需求需要结合 thymeleaf 中的一些功能

      sec:authorize=”isAuthenticated() :是否认证登录!来显示不同的页面

      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
      <div class="ui segment" id="index-header-nav" th:fragment="nav-menu">
      <div class="ui secondary menu">
      <a class="item" th:href="@{/index}">首页</a>

      <!--登录注销-->
      <div class="right menu">
      <!--如果未登录-->
      <!--<a class="item" th:href="@{/toLogin}">
      <i class="address card icon"></i> 登录
      </a>-->

      <div sec:authorize="!isAuthenticated()">
      <a class="item" th:href="@{/toLogin}">
      <i class="sign-out icon"></i> 登录
      </a>
      </div>

      <!--如果注销 用户名和注销按钮-->
      <!-- <a class="item" th:href="@{/logout}">
      <i class="sign-out icon"></i> 注销
      </a>-->

      <div sec:authorize="isAuthenticated()">
      <a class="item">
      用户名: <span sec:authentication="name"></span>
      </a>
      </div>

      <div sec:authorize="isAuthenticated()">
      <a class="item" th:href="@{/logout}">
      <i class="sign-out icon"></i> 注销
      </a>
      </div>

      </div>
      </div>
      </div>

      sec:authentication="name" 获取登录时用户名

      关于根据权限决定是否显示 这里只举一个例子

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <!--菜单根据用户的角色动态的实现-->
      <div class="column" sec:authorize="hasRole('vip1')">
      <div class="ui raised segment">
      <div class="ui">
      <div class="content">
      <h5 class="content">Level 1</h5>
      <hr>
      <div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>
      <div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>
      <div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>
      </div>
      </div>
      </div>
      </div>

      sec:authorize="hasRole('vip1')" 判断该用户是否有 vip1 的权限

      ==问题:==

      ​ 如果现在依旧按照视频中的配置进行配置的话会发现 thymeleaf 提供的这个功能并没有生效

      视频中选择的是降低版本 这种行为我不提倡. 其实出现这个问题的原因在于 thymeleafthymeleaf-extras-springsecurity 以及 Spring Security 的版本不兼容出现的问题

      这里给出 thymeleaf 官方文档一篇文章 可以去阅读一下 如果使用的是上面提供的 pom.xml 中的依赖应该是没有问题的

8、Shrio

官方文档

提前了解一些什么

了解 Shiro 的三大对象

  • Subject当前用户(与当前应用交互的任何东西)—所有 Subject 都绑定到 SecurityManager,与 Subject 的所有交互都会委托给 SecurityManager;可以把 Subject认为是一个门面;SecurityManager 才是实际的执行者;

  • SecurityManager : 管理所有用户,负责与后边介绍的其他组件进行交互

  • Realm: 安全数据源用于认证和授权ShiroRealm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色 / 权限进行验证.

img

img

总结:Subject表示当前用户,Security Manager管理所有用户,负责用户与其他组件的交互(认证,授权,Session管理..),从Realm中获取数据.

SpringBoot 整合 Shrio

导入 Shrio 的包

1
2
3
4
5
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring --><dependency>    
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.7.1</version>
</dependency>

配置 Shrio 的配置类

由上面的前置芝士可以发现 要使用 Shrio 就必须要有 Subject , SecurityManager, Realm 三层结构的配置所以在 SpringBoot 的配置包下需要有这些结构

由于 Reaml 层是没有默认实现的, 所以需要用户自己自定义的 所以需要用户自己去实现一个配置类

关于 UserRealm (用于了登录时的 认证授权) 配置类的自定义实现

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
//自定义的 realm
/*
继承了 AuthorizingRealm 就可以被 SpringBoot 认为是一个Realm 层的实现
而且必须要实现两个方法
1.protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) 实现在用户进行登陆时给与用户相应的权限
2.rotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException 实现用户在提交登录表单后进行后台的验证验证用户的用户名密码是否正确
具体某些认证授权方法在下面代码中有说明和解释
*/
public class UserRealm extends AuthorizingRealm {

@Autowired
UserService userService;

//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

//执行授权
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

//拿到当前登录的这个对象
Subject subject = SecurityUtils.getSubject();
User currentUser = (User) subject.getPrincipal(); //拿到 user 对象

//设置当前用户权限
info.addStringPermission(currentUser.getPerms());

return info;
}

//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
//密码认证, Shiro 来做

//连接真实数据库
User user = userService.queryUserByName(userToken.getUsername());
if (user == null) {
return null;
}
Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
session.setAttribute("loginUser", user);

return new SimpleAuthenticationInfo(user, user.getPwd(),"");
}
}

ShrioConfig

1
2
3
4
5
6
7
8
9
10
关于用户访问某个 url 时的拦截要求//
添加 Shiro 的内置过滤器
//添加 Shiro 的内置过滤器
/*
* anno 无需认证就可以访问
* authc 必须认证了才能访问
* user 必修拥有 记住我 功能才能使用
* perms 拥有对某个资源的权限才能访问
* role 拥有某个角色权限才能访问
* */
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
@Configuration
public class ShiroConfig {

@Bean
//ShiroFilterFactoryBean step 2
public ShiroFilterFactoryBean getShiroFilterFactoryBean(
@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);


Map<String, String> filterMap = new LinkedHashMap<>();

//拦截
/*filterMap.put("/user/add", "authc");
filterMap.put("/user/update", "authc");*/

//授权 正常情况下 没有授权会跳转到 未授权页面
filterMap.put("/user/add", "perms[user:add]");
filterMap.put("/user/update", "perms[user:update] ");


shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);

//设置返回登录页面的请求
shiroFilterFactoryBean.setLoginUrl("/toLogin");

//设置未授权页面
shiroFilterFactoryBean.setUnauthorizedUrl("/noauth");

return shiroFilterFactoryBean;
}

//DafaultWebSecurityManager step 2
@Bean(name = "getDefaultWebSecurityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联 UserRealm
securityManager.setRealm(userRealm);
return securityManager;
}

//创建 realm 对象 需要自定义 step 1
@Bean(name = "userRealm")
public UserRealm userRealm() { return new UserRealm(); }

@Bean
//整合 ShiroDialect : 用来整合 Shiro 和 thymeleaf
public ShiroDialect getShiroDialect() {
return new ShiroDialect();
}

}

由于 SpringBoot 为用户提供了默认实现的 SecurityManagerShiroFilterFactoryBean ,所以不需要用户先提供自定义的类才能将其配置类放置进 SpringBoot 的容器中 PS( @Bean(name=“xxx”) 表示可以不使用 Spring 容器提供的默认名称, 而是指定 Spring 容器中的对象名称为 “xxx”)

Shrio 整合 MyBatis

导入 MySQL Druid, MyBatispom 依赖

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
<!--druid 数据源-->
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.6</version>
</dependency>

<!--链接 Mysql 驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

<!--mybatis 整合 mybatis-spring-boot-starter 非 SpringBoot 官方-->
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>

配置 MyBatis 配置文件

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
spring:
datasource:
username:
password:
url: jdbc:mysql://47.100.227.175:3306/School?useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
# 配置 druid 数据源
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,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

1
2
# 配置 MyBatis
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml

**总结: ** 其实和之前整合数据库时没有什么区别

Shrio 整合 thymeleaf

导入 pom 依赖

1
2
3
4
5
6
7
<!--shiro-thymeleaf 整合-->
<!-- https://mvnrepository.com/artifact/com.github.theborakompanioni/thymeleaf-extras-shiro -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>

html 文件上加入命名空间

1
2
3
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">

Shriothymeleafhtml 中的使用

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
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">

<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>

<h1>首页</h1>

<h1 th:text="${msg}"></h1>

<div shiro:hasPermission="user:add">
<a th:href="@{/user/add}">add</a>
</div>
<div shiro:hasPermission="user:update">
<a th:href="@{/user/update}">update</a>
</div>


<div th:if="${session.loginUser==null}">
<a th:href="@{/toLogin}">登录</a>
</div>

<p> <a th:href="@{logout}">注销</a> </p>

</body>
</html>

ShrioSpringBoot 中的使用

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
@Controller
public class MyController {

@RequestMapping({ "/", "/index"})
public String toIndex(Model model) {
model.addAttribute("msg", "hello shiro");
return "index";
}

@RequestMapping("/user/add")
public String add() { return "User/add"; }

@RequestMapping("/user/update")
public String update() {
return "User/update";
}

@RequestMapping("/toLogin")
public String toLogin() { return "login"; }


@RequestMapping("/login")
public String login(String username,
String password, Model model) {
//获取当前用户
Subject subject = SecurityUtils.getSubject();
//封装用户的登录数据
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
subject.login(token); //执行登录的方法 如果没有异常就说明可以了
return "redirect:/index";
} catch (UnknownAccountException e) { //用户名不存在
model.addAttribute("msg","用户名错误");
return "login";
} catch (IncorrectCredentialsException e) { //密码不存在
model.addAttribute("msg", "密码错误");
return "login";
}
}

@RequestMapping("/noauth")
@ResponseBody
public String unAuthorized() {
return "未经授权无法访问此页面";
}

//注销
@RequestMapping("/logout")
public String logout() {
Subject currentUser = SecurityUtils.getSubject();
currentUser.logout();
return "redirect:/index";
}

}

扩展:

  • @qualifier 注解消除依赖注入冲突(当 SpringBoot 注入了相同的 Bean/Componet ),可以指明我们要用哪个

  • @primary 可以设置优先级

  • https://shiro.apache.org/java-authorization-guide.html 更多安全控制

  • thymeleaf 整合 shiro 的标签

    guest 标签
      shiro:guest
      
      用户没有身份验证时显示相应信息,即游客访问信息。

    user 标签
      shiro:user  
      
      用户已经身份验证/记住我登录后显示相应的信息。

    authenticated标签
      shiro:authenticated  
      
      用户已经身份验证通过,即 Subject.login 登录成功,不是记住我登录的。

    notAuthenticated 标签
      shiro:notAuthenticated
      
      
      用户已经身份验证通过,即没有调用 Subject.login 进行登录,包括记住我自动登录的也属于未进行身份验证。

    principal 标签
      <shiro: principal/>
      <shiro:principal property="username"/>  

    相当于 ((User)Subject.getPrincipals()).getUsername()

    lacksPermission 标签
      <shiro:lacksPermission name="org:create">
     
      
      如果当前 Subject 没有权限将显示 body 体内容。

    hasRole 标签
      <shiro:hasRole name="admin">  
      
      如果当前 Subject 有角色将显示 body 体内容。

    hasAnyRoles 标签
      <shiro:hasAnyRoles name="admin,user">
       
      
      如果当前 Subject 有任意一个角色(或的关系)将显示 body 体内容。

    lacksRole 标签
      <shiro:lacksRole name="abc">  
      
      如果当前 Subject 没有角色将显示 body 体内容。

    hasPermission 标签
      <shiro:hasPermission name="user:create">  
      
      如果当前 Subject 有权限将显示 body 体内容

    验证当前用户是否拥有指定权限
    添加

9、Swagger

Swagger简介

前后端分离时代

前端:控制层和视图层
后端:控制层,数据访问层,服务层

特点:
前端调用 >— 后端的API接口
即:松耦合

产生的问题:
前端和后端工作人员之间的交互问题(武器的变多)

解决
Swagger的诞生!

Swagger是什么?

  • 号称世界上最流行的API框架
  • Restful Api 文档在线自动生成器 => API 文档 与API 定义同步更新
  • 直接运行,在线测试API
  • 支持多种语言 (如:Java,PHP等)
  • 官网:https://swagger.io/

SpringBoot 整合 Swagger

导入 pom 依赖

编写 Swagger 配置类

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
@Configuration
//开启 swagger2
@EnableSwagger2
public class SwaggerConfig {

//配置 Swagger 的 docket 的 bean 实例
@Bean
public Docket docket(Environment environment){
//设置要显示 Swagger 环境
Profiles profiles = Profiles.of("dev", "test");
//获取项目环境:
//通过 environment.acceptsProfiles(profiles) 判断是否处在自己设定的环境之中
boolean flag = environment.acceptsProfiles(profiles);

return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.groupName("cu1")
//enalbe 是否启用 Swagger 如果为 False 则 Swagger 不能在浏览器中访问
.enable(flag)
.select()
/*
* basePackage() 配置要扫描接口的方式 basePackage() 基于某个包扫描
* any() 扫描全部
* none() 都不扫描
* 表示只会扫描类上有 @RestController 注解的接口
* withClassAnnotation(RestController.class) 扫描类上的注解 参数是一个注解的反射对象
* withMethodAnnotation(GetMapping.class) 扫描方法上的注解
* */
.apis(RequestHandlerSelectors.basePackage("com.cu1.swagger.Controller"))
//过滤路径 表示不扫描 某个路径下的所有文件
//.paths(PathSelectors.ant("/cu1/**"))
.build(); //工厂模式
}

//配置 Swagger 信息
private ApiInfo apiInfo() {

//作者信息
Contact contact = new Contact("Cu1",
"https://blog.csdn.net/CUCUC1?spm=1000.2115.3001.5343",
"cu1universe@gmail.com");

return new ApiInfo("Cu1's SwaggerAPI document",
"Wuhu Qifei",
"1.0",
"https://blog.csdn.net/CUCUC1?spm=1000.2115.3001.5343",
contact,
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0",
new ArrayList());
}

}

测试类

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
@RestController
public class HelloController {

@GetMapping(value = "/hello")
public String Hello() {
return "hello";
}

//只要接口中返回值中存在实体类 它就会被扫描
@PostMapping("/user")
public User user() {
return new User();
}

// Operation 接口 不是放在类上 是放在方法上 给接口方法提供注释
@ApiOperation("Hello 控")
@GetMapping("/hello2")
//@ApiParam("用户名") 给接口参数加注释
public String hello(@ApiParam("用户名") String username) {
return "hello" + username;
}

@ApiOperation("post 测试")
@PostMapping("/postt")
public User postt(@ApiParam("用户名") User user) {
return user;
}

}

常用注解

Swagger的所有注解定义在io.swagger.annotations包下

下面列一些经常用到的,未列举出来的可以另行查阅说明:

Swagger注解 说明
@ApiModel(”说明这是什么类”) 用于pojo下的实体类
@ApiModelProperty(value=”属性说明”,hidden=ture) 用于实体类下的属性,hidden为true表示可以隐藏
@Api(“模块说明”) 用于controller层继续模块说明
@ApiOperation(”方法说明”) 用于controller层下的方法
@ApiParm(“参数说明”) 用于controller层下的方法参数

希望 Swagger 在生产环境中使用 在发布后不能使用

  • 判断是不是生产环境 flag = false
  • 注入 enable(flag)

配置 API 文档分组

1
.groupName("cu1")

配置多个 API 文档 向 Spring 容器中配置多个 Docket 实例即可

总结:

  1. 可以通过 Swagger 给一些比较难理解的属性或者接口, 增加注释信息

  2. 接口文档实时更新

  3. 可以在线测试

注意点 : 在正式发布时 关闭 Swagger 出于安全考虑 而且节省内存空间

扩展:皮肤!!

我们可以导入不同的包实现不同的皮肤定义:

  1. 默认的 访问 http://localhost:8080/swagger-ui.html
1
2
3
4
5
6
<!-- 引入swagger-ui-layer包 /docs.html-->
<dependency>
<groupId>com.github.caspar-chen</groupId>
<artifactId>swagger-ui-layer</artifactId>
<version>1.1.3</version>
</dependency>
  1. bootstrap-ui 访问 http://localhost:8080/doc.html
1
2
3
4
5
6
<!-- 引入swagger-ui-layer包 /document.html-->
<dependency>
<groupId>com.zyplayer</groupId>
<artifactId>swagger-mg-ui</artifactId>
<version>1.0.6</version>
</dependency>
  1. Layui-ui 访问 http://localhost:8080/docs.html
1
2
3
4
5
6
<!-- 引入swagger-ui-layer包 /docs.html-->
<dependency>
<groupId>com.github.caspar-chen</groupId>
<artifactId>swagger-ui-layer</artifactId>
<version>1.1.3</version>
</dependency>
  1. mg-ui 访问 http://localhost:8080/document.html
1
2
3
4
5
6
<!-- 引入swagger-ui-layer包 /document.html-->
<dependency>
<groupId>com.zyplayer</groupId>
<artifactId>swagger-mg-ui</artifactId>
<version>1.0.6</version>
</dependency>

10、关于异步任务

请浏览 https://www.jianshu.com/p/20a4e37314fc

邮件发送

image-20211109164724576

开启第一个

导入 pom 依赖

1
2
3
4
5
<!--javax.mail: 配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>

调用 SpringBoot 的接口即可

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
@Autowired
JavaMailSenderImpl mailSender;

@Test
void contextLoads() {
//一个简单的邮件
SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
simpleMailMessage.setSubject("Cu1 Hello");
simpleMailMessage.setText("芜湖起飞");
simpleMailMessage.setTo("1178079301@qq.com");
simpleMailMessage.setFrom("1178079301@qq.com");
mailSender.send(simpleMailMessage);
}
@Test
void contextLoads1() throws MessagingException {
//一个复杂的邮件
MimeMessage message = mailSender.createMimeMessage();
//组装 利用 Helper 去组装 message 是否支持多文本上传 第二个参数
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(message, true, "UTF-8");
mimeMessageHelper.setSubject("Cu1 hello - plus");
//第二参数为是否将消息解析为 html
mimeMessageHelper.setText("<p style='color: red'>芜湖起飞</p>", true);
//添加附件
//mimeMessageHelper.addAttachment("1.jpg", new File());
mimeMessageHelper.setTo("1178079301@qq.com");
mimeMessageHelper.setFrom("1178079301@qq.com");
mailSender.send(message);
}

定时任务

1
2
3
4
5
TaskScheduler 任务调度者
TaskExecutor 任务执行者
//开启定时功能的注解
@EnableScheduling
@Scheduled

使用

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

//Cron 表达式
// 秒 分 时 日 月 周几 (表示 22H 13Min 0S 执行)
@Scheduled(cron = "0 13 22 * * ?")
//在特定的时间执行这个方法
public void hello() {
System.out.println("hello 你被执行了");
}

}

11、SpringBoot 整合 Redis

源码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate //可以自定义一共 redisTemplate 来替换默认的
)
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
//默认的 RedisTemplate 没有过多设置 redis 对象都需要序列化
//两个泛型都是 Object Object 的类型 在后面使用中通常需要强制转换为 <String, Object>
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}

@Bean
@ConditionalOnMissingBean //由于 String 是 redis 中最常用的类型 所以说单独提出来一个 Bean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}

整合测试

1、导入依赖

1
2
3
4
5
<!--操作 redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、配置连接

1
2
3
4
#自动配置类都会绑定一个 properties 配置文件 RedisProperties
#配置 redis
spring.redis.host=127.0.0.1
spring.redis.port=6379

3、测试

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
@SpringBootTest
class Redis02SpringBootApplicationTests {

@Autowired
private RedisTemplate redisTemplate;

@Test
void contextLoads() {

/*
* RedisTemplate 操作不同的数据类型 api 和 指令是一样的
* .opsForValue() 操作字符串 类似 String
* .opsForList() 操作 List
* .opsForSet() 操作 Set
* .opsForHash()
* .opsForZSet() ...
* .opsForGeo()
* 除了基本操作 常用的方法都可以通过 RedisTemplate 操作 比如事务和基本的 CRUD
* */
//获取 redis 的连接对象
/*
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
connection.flushDb();
connection.flushAll();
*/
redisTemplate.opsForValue().set("myKey", "Cu1");
System.out.println(redisTemplate.opsForValue().get("mykey"));
}

}

RedisTemplate 序列化配置

默认是使用 JDK 的序列化方式 但是可能需要使用 json 进行序列化 这时就需要自定义配置类

==关于对象的保存 如果没有序列化则会报错== 在企业中 所有的 pojo 类都会序列化

自定义 @Qualifier()

编写一个自定义的 RedisTemplate 如果出现多个 RedisTemplate 可以使用 @Qualifier() 来指定自定义的 @Qualifier()

在企业开发中 有 80% 的情况下都不会使用原生的方式去编写代码

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
@Configuration
public class RedisConfig {

//固定模板 在企业中直接使用
//编写自己的 RedisTemplate
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
//为方便开发 一般直接使用 <String, Object>
RedisTemplate<String, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
//创建一个序列化方式的对象 序列化配置
//Json 序列化
Jackson2JsonRedisSerializer<Object> objectJackson2JsonRedisSerializer
= new Jackson2JsonRedisSerializer<Object>(Object.class);

ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
objectJackson2JsonRedisSerializer.setObjectMapper(om);

//String 的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
//key 采用 String 的序列化方式
template.setKeySerializer(stringRedisSerializer);

//hash 的 key 也采用 String 的序列化方式
template.setHashKeySerializer(stringRedisSerializer);

//Value 序列化方式采用 Jackson
template.setValueSerializer(objectJackson2JsonRedisSerializer);

//配置具体的序列化方式
template.setKeySerializer(objectJackson2JsonRedisSerializer);

//hash 的 value 序列化方式采用 jackson
template.setHashValueSerializer(objectJackson2JsonRedisSerializer);

template.afterPropertiesSet();
return template;
}
}

RedisUtil 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
@Component
@SuppressWarnings("all")
public class RedisUtil {
@Resource
private RedisTemplate<String, Object> redisTemplate;

/*
* @ClassName RedisClient
* @Desc TODO 设置缓存(没有时间限制)
* @Date 2021-07-24 16:11
* @Version 1.0
*/
public void set(String key, Object value) {
redisTemplate.opsForValue().set(key, value);
}

/*
* @ClassName RedisClient
* @Desc TODO 设置缓存(有时间限制,单位为 秒)
* @Date 2021-07-24 16:11
* @Version 1.0
*/
public void set(String key, Object value, long timeout) {
redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
}

/*
* @ClassName RedisClient
* @Desc TODO 删除缓存,并返回是否删除成功
* @Date 2021-07-24 16:11
* @Version 1.0
*/
public boolean delete(String key) {
redisTemplate.delete(key);
// 如果还存在这个 key 就证明删除失败
if (redisTemplate.hasKey(key)) {
return false;
// 不存在就证明删除成功
} else {
return true;
}
}

/*
* @ClassName RedisClient
* @Desc TODO 取出缓存
* @Date 2021-07-24 16:12
* @Version 1.0
*/
public Object get(String key) {
if (redisTemplate.hasKey(key)) {
return redisTemplate.opsForValue().get(key);
} else {
return null;
}
}

/*
* @ClassName RedisClient
* @Desc TODO 获取失效时间(-2:失效 / -1:没有时间限制)
* @Date 2021-07-24 16:15
* @Version 1.0
*/
public long getExpire(String key) {
// 判断是否存在
if (redisTemplate.hasKey(key)) {
return redisTemplate.getExpire(key);
} else {
return Long.parseLong(-2 + "");
}
}
}