# 概述

SpringBoot 是个好东西,简化了很多,尤其是配置,很少的配置就能使得项目运行。

Spring Boot 基于 Spring 开发,Spirng Boot 本身并不提供 Spring 框架的核心特性以及扩展功能,只是用于快速、敏捷地开发新一代基于 Spring 框架的应用程序。Spring Boot 以约定大于配置的核心思想,默认帮我们进行了很多设置,多数 Spring Boot 应用只需要很少的 Spring 配置。同时它集成了大量常用的第三方库配置(例如 Redis、MongoDB、Jpa、RabbitMQ、Quartz 等等),Spring Boot 应用中这些第三方库几乎可以零配置的开箱即用。

# 创建

使用官方的 Spring Initializr 生成 SpringBoot 项目:https://start.spring.io/

1644560692927

当然 IDEA 也带有 SpringBoot 新建功能,只需要选好配置和依赖就行。

image-20230306155307807

# 版本

SpringBoot 支持

目前(2023.03)Springboot 已经发布了 3.1.x 版本,但是 3 版本最低要求 JDK17。而 JDK8 可以使用 2.7.x 版本。

# 项目类型

首先对于项目的创建可以选择基于 Mevan 或是 Gradle,这里要提一下 Gradle:

Gradle 是一个基于 Apache Ant 和 Apache Mevan 概念的项目自动化建构工具。

构建脚本使用 Groovy 和 Kotlin 的特定领域语言来编写的,不是传统的 XML

groupid 和 artifactId 被统称为 “坐标” 是为了保证项目唯一性而提出的,如果你要把你项目弄到 maven 本地仓库去,你想要找到你的项目就必须根据这两个 id 去查找。

# 打包方式

jar 与 war 区别:

jar 是类的归档文件(JAVA Archive)是与平台无关的文件格式,它允许将多个组合成一个压缩文件,jar 文件格式以 zip 文件格式为基础。同时,jar 文件不仅用于压缩和发布,还用于部署和封装库、组件和插件程序,并可被编译器和 JVM 这样的工具直接使用。

war 包是一个 Web 应用程序,里面包含我们 web 程序需要的一些东西,其中包括 web.xml 的配置文件,前端的页面文件,以及依赖的 jar。便于我们部署工程,直接放到 tomcat 的 webapps 目录下,直接启动 tomcat 即可。同时,可以使用 WinRAR 查看 war 包,直接将后缀.war 改成.rar。

Web 存档 (war) 文件包含 Web 应用程序的所有内容。它减少了传输文件所需要的时间。

区别:

SpringBoot 项目既可以打成 war 包发布,也可以找成 jar 包发布。

jar 包:直接通过内置 Tomcat 运行,不需要额外安装 Tomcat。如需修改内置 Tomcat 的配置,只需要在 SpringBoot 的配置文件中配置。内置 Tomcat 没有自己的日志输出,全靠 jar 包应用输出日志。但是比较方便,快速,比较简单。

war 包:传统的应用交付方式,需要安装 Tomcat,然后放到 wabapps 目录下运行 war 包,可以灵活选择 Tomcat 版本,可以直接修改 Tomcat 的配置,有自己的 Tomcat 日志输出,可以灵活配置安全策略,相对打成 jar 包来说没那么快速方便。

# 依赖

在创建 springboot 项目时可以添加自己需要的依赖,只需要点击 ADD DEPENDENCIES 或者 ctrl + B 查找依赖进行添加


# 项目目录

项目下载解压后目录:

1
2
3
4
5
6
7
8
9
10
├─.idea
├─.mvn
│ └─wrapper
├─src
│ ├─main
│ │ ├─java
│ │ └─resources
│ └─test
│ └─java
└─target

# git 和 maven 相关文件

# .gitignore

分布式版本控制系统 git 的配置文件,忽略提交

在.gitignore 文件中,遵循相应的语法,在每一行指定一个忽略规则如:

.log

.temp

/vendor

# mvnw

maven wrapper 缩写

在 maven-wrapper.properties 文件中记录你要使用的 maven 版本,当用户执行 mvnw clean 命令,发现当前用户的 mevan 版本和期望的版本不一致,那么就下载期望的版本,然后用期望的版本来执行 mvn 命令

# .mvn 文件夹

存放 maven-wrapper.properties 和相关 jar 包

# mvn.cmd

执行 mvnw 命令的 cmd 入口

# resouces 目录

# static

存放静态资源

# templates

存放模板资源

freemaker 相关

# 配置文件

springboot 默认读取全局配置文件:

application.yam 或 application.properties 文件

  • yml 文件格式:yaml(Ain't a Markup Language)

    一种直观的能够被电脑识别的数据序列化格式,并且容易被人类阅读

文件默认在 resources 目录下读取,使用配置文件修改默认配置

prooperties 文件格式:

1
server.port=8080

yml 文件格式:

1
2
server:
port:8080

# profile

Profile 是 Spring 用来针对不同环境对不同配置提供全局的 Profile 配置,使用 application-{Profile}.yml

主配置文件 application.yml

1
2
3
4
##环境选择配置
spring:
profiles:
active:"{profile}"

工程当中我们会使用三种配置文件对应不同的环境:

  • dev 开发环境
  • test 测试环境
  • pro 正式环境

也可以使用 application-dev.xml ,然后在项目启动时加上 -Dspring.profiles.active=dev 来使用开发配置启动项目,在本地 idea 开发时可以在启动配置中加入这个参数:

image-20230306160513285

启动时会打印相关信息:

1
2023-03-06 16:04:46.031  INFO 38292 --- [main] n.b.s.SecurityDepartmentApplication      : The following 1 profile is active: "dev"

# 日志

使用 web 自动引入依赖

application.yml:

1
2
3
4
5
6
logging:
pattern:
level: DEBUG
file:
name: "springboot.log"
path: "./"

# Start 坐标

SpringBoot 引入全新坐标体系,简化 pom.xml

使用 Spring MVC 来构建 RESTful Web 应用,并使用 Tomcat 作为默认容器

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

集成视图技术

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

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

Spring-boot 项目引入 starter 系列坐标,对应的版本库统一由父工程坐标统一控制,即:

1
2
3
4
5
6
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.4</version>
<relativePath/>
</parent>

<relativePath/> 设定一个空值将始终从仓库中获取,不从本地路径获取

# 自动化配置

​ SpringBoot 的项目一般都会有 * Application 的入口类,入口类中提供 main 方法,这是一个标准的 Java 应用程序的入口方法。 @SpringBootApplication 注解是 SpringBoot 的核心注解,它其实是一个组合注解。

1644566269748

# 元注解概述

JDK1.5 开始,Java 增加了对元数据的支持,也就是 Annotation

注解就是就是代码中的特殊标记,用于替代配置文件

# meta-annotation

​ 除了 JDK 定义好的注解,我们还可以自定义注解,JDK1.5 提供了四个标准用来对注解类型进行注解的注解类

  • @Target

  • @Retention

  • @Document

  • Inherited

# @Target 注解

描述注解的使用范围

Target 注解用来说明被注解的注解类可修饰的对象的范围:

​ 注解可以修饰 packages types(类、接口、枚举、注解类)、类成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量

取值范围在 ElementType 枚举中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public enum ElementType {

TYPE, // 类、接口、枚举类

FIELD, // 成员变量(包括:枚举常量)

METHOD, // 成员方法

PARAMETER, // 方法参数

CONSTRUCTOR, // 构造方法

LOCAL_VARIABLE, // 局部变量

ANNOTATION_TYPE, // 注解类

PACKAGE, // 可用于修饰:包

TYPE_PARAMETER, // 类型参数,JDK 1.8 新增

TYPE_USE // 使用类型的任何地方,JDK 1.8 新增

}

# @Retention 注解

描述注解保留的时间范围

​ 限定所注解的注解类注解到其他类上后,可以被保留到何时,一共三个策略,定义在 RetentionPolicy 枚举中:

1
2
3
4
5
public enum RetentionPolicy {
SOURCE, // 源文件保留
CLASS, // 编译期保留,默认值
RUNTIME // 运行期保留,可通过反射去获取注解信息
}

# @Documented 注解

描述在使用 javadoc 工具为类生成帮助文档时是否需要保留注解信息

# @Inherited 注解

使被修饰的注解具有继承性


# 总结

​ SpringBoot 通过 maven 中的 starter 导入了所需场景下的 jar 包,并通过主启动类 @SpringBootApplication 中的 @EnableAutoConfiguration 读取该 jar 路径下的 META—INF/spring.factories 下 EnableAutoFiguration 的配置类,这些配置类使用 @ConditionOnClass 来标注,根据 @ConditionOnClass 约束条件引入自动化环境的配置。

# 项目使用

# 开启

使用 war 包就打包为 war 包放到 tomcat 容器下

jar 包使用 java -jar 包名 运行

# 关闭

springboot 关闭貌似比较麻烦

# 使用 Actuator

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

​ SpringBoot 内置监控功能 Actuator,添加依赖后正常启动项目,Actuator 会把收集的所有信息暴露给 JMX(Java Management Extensions)

Spring Boot Actuator 端点通过 JMX 和 HTTP 公开暴露给外界访问,大多数时候我们使用基于 HTTP 的 Actuator 端点,因为它们很容易通过浏览器、CURL 命令、shell 脚本等方式访问。

常用端点:

  1. /beans:此端点返回应用程序中配置的所有 bean 的列表。
  2. /env:提供有关 Spring Environment 属性的信息。
  3. /health:显示应用程序运行状况
  4. /info:显示应用程序信息,我们可以在 Spring 环境属性中配置它。
  5. /mappings:显示所有 @RequestMapping 路径 的列表 。
  6. /shutdown:允许我们正常关闭应用程序。
  7. /threaddump:提供应用程序的线程转储。

# 全局异常处理

# @ControllerAdivice

组合注解,组合了 @Componet 注解,常用全局异常处理切面类,同时该注解可以指定扫描包范围

# @ExceptionHandler

spring3.x 引入,处理异常时标注到方法,代表当前方法处理异常有哪些

1644806313560

# 数据校验

# validation

日常项目开发中,对于前端提交的表单,后台接口接收到表单数据后,为了程序的严谨性,通常后端会加入业务参数的合法校验操作来避免程序的非技术性 bug, 这里对于客户端提交的数据校验,SpringBoot 通过 spring-boot-starter-validation 模块包含了数据校验的工作。

相关概念:

  • JSR303:是一项标准,只提供规范不提供实现,规定一些校验规范即校验注解,如 @Null、@NouNull、@Pattern、位于 javax.validation.constrains 包下。JSR-349 是升级版本
  • Hibernate Validation:对以上规范的实现,并增加了一些其他校验,如 @Email、@Length、@Range 等等
  • Spring Validation :对 Hibernate Validation 的二次封装,在 SpringMVC 模块添加了自动校验,并将校验信息封装进了特定的类中

约束性注解:

注解说明
@AssertFalse可以为 null,如果不为 null 的话必须为 false
@AssertTrue可以为 null,如果不为 null 的话必须为 true
@DecimalMax设置不能超过最大值
@decimalMin设置不能超过最小值
@Digits设置必须是数字且数字整数的位数和小数的位数必须在指定范围内
@Future日期必须先在当前日期未来
@Past日期必须在当前日期过去
@Max最大不得超过此最大值
@Min最小不得小于最小值
@NotNull不能为 null 可以是空
@Null必须 null
@Pattern必须满足指定的正则表达式
@Size集合、数组、map 等的 size () 不能为 0;字符串 trim () 后可以等于 “”
@Email必须是 email 格式
@Length长度必须在指定范围内
@NotBlank字符串不能为 null,字符串 trim () 后不能为 “”
@NotEmpty不能为 null,集合、数组、map 等 size () 不能为 0;字符串 trim () 后可以等于 “”
@Range值必须在指定范围内
@URL必须是一个 URL

配合全局异常捕捉

1
2
3
4
5
6
@ExceptionHandler(Exception.class)
public Map<String, Object> exceptionMessageReturn(BindException b){
Map<String, Object> map = new HashMap<>();
map.put("msg",b.getBindinResult(),getFieldError().getDefaultMessage());
return map;
}

# 分布式缓存 Ehcache 整合

Ehcache 是一个成熟的 java 缓存框架,最早从 hibernate 发展而来,是进程中的缓存系统,它提供了用内存,磁盘文件存储,以及分布式存储的方式等多种灵活的 cache 管理方案,快速简单

​ springboot 对 Ehcache 的使用提供支持,所以在 springboot 中只需要简单配置即可使用 Ehcache 实现数据缓存处理。

依赖:

1
2
3
4
5
6
7
8
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>

# 缓存配置文件

1644892317917

1644892332292

encache-config.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
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:NamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">

<!-- 默认缓存 -->
<defaultCache
maxEntriesLocalHeap="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
maxEntriesLocalDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<persistence strategy="localTempSwap"/>
</defaultCache>

<!-- helloworld缓存 -->
<cache name="HelloWorldCache"
maxElementsInMemory="1000"
eternal="false"
timeToIdleSeconds="5"
timeToLiveSeconds="5"
overflowToDisk="false"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>

也可以:

1
2
3
4
5
6
Cache cache = manager.getCache("mycache"); 
CacheConfiguration config = cache.getCacheConfiguration();
config.setTimeToIdleSeconds(60);
config.setTimeToLiveSeconds(120);
config.setmaxEntriesLocalHeap(10000);
config.setmaxEntriesLocalDisk(1000000);

# SpringCache 相关注解说明

​ springBoot 缓存实现内部使用 springcache 实现缓存机制,这里集成 Ehcache 实际上是对 springCache 的抽象的一种实现。

# @CacheConfig

用于标注类上,可以存放该类中所有缓存的公有属性,比如缓存名

1
2
3
4
@CacheConfig(cacheName = "users")
pulic interface UserService{

}

​ 配置了该数据访问对象中返回的内容将存储于名为 users 的缓存对象中,我们也可以不使用该注解,直接通过 @Cacheable 自己配置缓存集的名字来定义。

# @Cacheable

​ 应用到读取数据的方法上,即可缓存的方法,如查找方法,先从缓存中读取,如果没有就调用相应方法获取数据,然后把数据添加到缓存中。

该注解有以下参数:

  • value、cacheNames : 两个等同的参数 (cacheNames 为 Spring4 新增,作为 value 的别名),用于指定缓存存储的集合名。由于 Spring4 中新增了 @CacheConfig, 因此在 Spring 了中原本必须有的 value 属性,也成为非必需项了
  • key: 缓存对象存储在 Map 集合中的 key 值,非必需,缺省按照函数的所有参数组合作为 key 值,若自己配置需使用 SqL 表达式,比如: @Cacheable (key = "#p0") : 使用函数第一个 参数作为缓存的 key 值,更多关于 SpEL 表达式的详细内容可参
    考官方文档。
  • condition : 缓存对象的条件,非必需,也需使用 SpEL 表达式,只有满足表达式条件的内容才会被缓存,比如:
    @Cacheable (key = "#p0", condition = "#p0.length ()< 3"),表示只有当第一个 参数的长度小于 3 的时候才会被缓存。
  • unless : 另外一个缓存条件参数,非必需,需使用 SpEL 表达式。它不同于 condition 参数的地方在于它的判断时机,该条件是在函数被调用之后才做判断的,所以它可以通过对 result 进行判断。
  • keyGenerator : 用于指定 key 生成器,非必需。若需要指定一个自定义的 key 生成器,我们需要去实现
    org.springframework.cache.interceptor.KeyGenerator 接口,并使用该参数来指定。需要注意的是:该参数与 key 是互斥
    的。
  • cacheManager : 用于指定使用哪个缓存管理器,非必需。只有当有多个时才需要使用
  • cacheResolver : 用于指定使用那个缓存解析器,非必需。需通过 org.springframework.cache.interceptor.CacheResolver
    接口来实现自己的缓存解析器,并用该参数指定。

# 缓存过期策略

EhCache 提供了三种淘汰算法:

  • FIFO:First In First Out,先进先出。判断被存储的时间,离目前最远的数据优先被淘汰。
  • LRU:Least Recently Used,最近最少使用。判断最近被使用的时间,目前最远的数据优先被淘汰。
  • LFU:Least Frequently Used,最不经常使用。在一段时间内,数据被使用次数最少的,优先被淘汰。

​ Ehcache 采用的是懒淘汰机制,每次往缓存放入数据时,都会存一个时间,在读取时要和设置的时间做 TTL 比较来判断是否过期。

# 单元测试

控制层单元测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@SpringBootTest
@AutoConfigureMockMvc
public class TestController {
private MockMvc mockMvc;
@Test
public void test() throws Exception {

//构建请求
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("")
.contentType("text/html")
.accept(MediaType.APPLICATION_JSON);

ResultActions perform = mockMvc.perform(builder);

perform.andExpect(MockMvcResultMatchers.status().isOk());

MvcResult result = perform.andReturn();

MockHttpServletResponse response = result.getResponse();

System.out.println(response.getContentAsString());
}
}

# 热部署

热部署,就是再应用正在运行的时候升级软件(增加业务 / 修改 bug),而不需要重启应用。

​ 项目开发过程中,常常会修改页面数据或数据结构,为了显示改动效果,往往需要重新启动应用,查看变动效果,其实就是重新编译生成了 class 文件,这个文件里记录了与代码对应的各种信息,然后 class 文件将被虚拟机 ClassLoader 加载。

​ 而热部署正是利用了这个特点,它监听到如果有 Class 文件改动了。就会创建一个新的 ClassLoader 进行加载改文件,经过一系列的过程,最终将结果呈现在我们眼前,SpringBoot 通过配置 DevTools 工具来达到热部署效果。

​ 在原理上是使用了两个 ClassLoader,一个 ClassLoader 加载哪些不会改变的类,另一个 ClassLoader 加载会改变的类,称为 resrart ClassLoader,这样在有代码更改的时候,原来的 restart ClassLoader 被丢弃,重新创建一个 restart ClassLoader,由于需要加载的类相比较少,所以实现了较快的重启时间,。

1
2
3
4
5
6
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<!--当前项目被继承后不向下传递-->
<optional>true</optional>
</dependency>

​ devtools 可以实现页面热部署(即页面修改后会立即生效,这个可以直接再 application.yml 文件中配置 spring.thymeleaf.cache=true 实现),实现类文件热部署(类文件修改后不会立即生效),实现对属性文件的热部署。即 devtools 会监听 classpath 下的文件变动,并且会立即重启应用(发生在保存实际),注意,以为其采用虚拟机机制,该项目重启是很快的。配置了后在修改 java 文件后也就支持了热启动,不过这种方式属于项目重启(速度比较快的项目重启),会清空 session 中的值,也就是如果有用户登录的话,项目重启后需要重新登录。

​ 默认情况下,/META-INF/maven,/META-INF/resources,/resources , /static/templates, /public 这些文件修改不会使应用重启,但会重新加载(devtools 内嵌了一个 LiveReload server,当资源发生改变时,浏览器刷新

配置文件:

1
2
3
4
5
6
7
8
spring:
devtools:
restart:
enabled: true
additional-paths: src/main/java/
# 解决项目自动重新编译后报404问题
poll-interval: 3000
quiet-period: 1000

配置 spring.devtools.restart.enabled=true 后 restart 类加载器还会初始化,但不会监视文件更新

修改 java 类后,idea 默认是不自动编译的,而 spring-boot-devtools 又是监测 classpath 下的文件发生变化才重新启动应用,所以需要设置 idea 重新编译

1644929563356

之后启动应用 ctrl+F9 重新启动即热部署成功

# API 文档构建 - swagger2

​ 由于 SpringBoot 能够快速开发、便捷部署等特性,通常再使用 SpringBoot 构建 RESTful 风格接口应用时考虑到多终端的原因,这些终端会共用很多底层逻辑,因此我们会抽象出这样一层来同时服务于多个移动端或者 Web 前端。对于不同的终端公用一套接口 API 时,对于联调测试的时候就知道后端提供的接口 Api 列表文档,对于服务端开发人员来说就需要编写接口文档,描述接口调用地址、参数结果等,这里借助第三方构建工具 Swagger2 来实现 API 文档生成功能。

依赖添加:

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId> <version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>

常用注解:

# @Api

用在请求的类上,说明该类作用

1
@Api(tags="App用户注册Controller")

# @ApiOperation

@ApiOperation: 用在请求方法上,说明方法作用

value = 说明方法作用

notes = 方法的备用说明

1
@ApiOperation(value="用户注册",notes="手机号、密码都是必填项,年龄是选填项,但必须数字")

# @ApiImplicitParams

1
2
3
4
5
6
7
8
9
10
11
12
13
@ApiImplicitParams用在方法上,包含一组参数说明
@ApiImplicitParam用在ApiImplicitParams注解中,指定一个请求参数的配置信息
name:参数名
value:参数的汉字说明、解释
required:参数是否必须传
paramType:参数放在哪个地方
· header --> 请求参数的获取: @RequestHeader
· query --> 请求参数的获取: @RequestParam
· path(用于REST接口) --> 请求参数的获取:@PathVariable
· body(不常用)
form(不常用)
dataType: 参数类型,默认String, 其他值dataType="Integer"
dafaultValue: 参数默认值

1
2
3
4
@ApiImplicitParams({
@ApiImplicitParam(name="mobile", value="手机号",required=true,paramType="form"),
@ApiImplicitParam(name="password", value="密码",required=true,paramType="form"),
})

# @ApiResponses

1
2
3
4
5
@ApiResponse:用于请求的方法上,表示一组响应
@ApiResponse:用在@ApiResponses中,一般用于表达一个错误的响应信息
code:数字 400
message:信息
response:抛出异常的类

# @ApiModel

用于响应类上,表示一个返回响应数据的信息(一般在 post 创建的时候,使用 @RequestBody 这样的场景,请求参数无法使用 @ApiImplicitParam 注解进行描述的时候)

@ApiModelProperty:用在属性上,描述响应类的属性

# Quartz 定时任务

在日常项目运行中,我们总会有需求在某一时间段周期性执行某个动作。比如每天再某个时间段导出报表,或者每隔多久统计一次现在在线的用户量等。

​ 在 SpringBoot 中有 Java 自带的 Java.util.Timer 类,也有强大的调度器 Quartz,还有 SpringBoot 自带的 Scheduled 来实现。Scheduled 在 Spring3.X 引入,默认 SpringBoot 自带该功能,使用起来也很简单,在启动类级别添加 @EnableScheduling 注解即可引入定时任务环境。但遗憾的是 Scheduled 默认不支持分布式环境,这里主要讲解 Quartz 时钟调度框架与 SpringBoot 集成。

依赖:

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

更新于 阅读次数 本文阅读量:

请我喝[茶]~( ̄▽ ̄)~*

Windlinxy 微信支付

微信支付

Windlinxy 支付宝

支付宝