梦入琼楼寒有月,行过石树冻无烟

📖 earlier posts 📖

Spring cloud Sleuth and Zipkin

Sleuth 主要用于日志采样,而 Zipkin 用于实现服务链路追踪,他们分别是微服务中最为常用的项目之一,其中 Zipkin 可以实现数据存储(支持 ES、MySQL、cASSANDRA)以及简单部署和文档完善等优势深受开发者喜爱。

Sleuth 由 Spring cloud 团队借助 Google Dapper、Twitter Zipkin、Apache Htrace 进行设计。而 Zipkin 还有一个 OpenZipkin 项目,是 Zipkin 的完全开源版本,项目由 2021年起源与 Twitter,同样基于 Google Dapper 论文进行实现。

Dapper,一种大规模分布式系统跟踪基础设施
https://research.google/pubs/pub36356/

首先,我们需要来介绍下服务链路追踪的作用,现在很多文章一开始就跟你巴拉巴拉 Zipkin、HTrace 原理什么的,他们的基本概念就是监控微服务状态,及时发现并定位问题的点,并快速解决问题。

对于服务链路追踪,蛀牙的就是实现监控为服务系统,来提供及时且准确的性能报告,并统计请求所耗费的时间以及具体网络延迟情况并辅助开发/运维人员分析并解决系统所存在的问题。

其实最为主要的还是通过他来快速定位故障,这也是解决问题最主要的原因 ——发现问题,通过服务链路追踪可以定位问题的故障点,并采取措施来解决问题,并提供快速检测或预警,隔离和修复问题等方式。

Google Dapper

Google Dapper 是一个在 2010 年 5 月由 Benjaim H. Sigelman, Luiz Andr’e Burrows, Pat Stephenson, Manoj Plakal, Donald Beaver, Saul jasper, Chandan shanbhag 发布于谷歌技术报告中的一篇关于大规模分布式系统跟踪的一篇论文。

他奠定了之后的 Sleuth 以及 Zipkin 的开发与实施,都提供了前期的架构和一种概念,起初 Dapper 是一个自包含的跟踪工具,后来发展为一个监控平台。

跨度 (Span)

跨度(Span)这是一个在 Dapper 中的一个基本工作单位,发送一个 RPC 请求,就是一个新的 Span,其中还包含了其他数据,例如描述、时间戳时间、注释这些。

从上图中,客户端(前端请求)所调用的服务单元,这个过程就是一次 Span ,发起请求的客户端或前端请求的 Span id:1 ,而下一个请求的 Span id:2 ……

他们都是一个父集关系因此 Span 还有一个 parent id 也就是父集 ID,父集的 ID 为 no parent id 也被称之为 root parent,那么 Span id:2 的服务自然为parent id:1

跟踪 (Trace)

之后为了将 Span 记录的更加完整,也就是最终形成一个完整的树状结构,那么也会有一个 trace id,他的主要作用就是将 Span 标注为一个组中,在可视化界面中更加的好理解。

标注 (Annotation)

用于记录事件的存在,可以定义请求的开始和停止等信息,例如客户端发送了一个请求,Annotation 会描述的开始、服务器端获得、准本开始处理:

Id Name Info
1 cs, Client Sent 客户端发送,客户端发送了请求
2 sr,Server Received 服务端收到了请求并开始处理(根据时间戳可以从 cs ~ sr 这段时间减时间戳获取网络延迟)
3 ss,Server Sent 服务器发送,在请求处理完成时进行标注(Annotation),这个时间戳减去 sr 的时间戳,就可以得到服务器端处理所需要的时间
4 cr,Client Received 客户端收到,表示 Span 结束,客户端已经成够收到了服务器端发送的相应(因此时间戳减去 cs 就是客户端从服务器接受响应所需的全部时间)

Sleuth 日志采样

Sleuth 与 Spring Cloud 高度契合,兼容了 Zipkin、HTrace、Log-based 追踪为服务调用链路,而我们用 Sleuth 主实现的就是返回 Span\Trace id,主要使用到 spring-cloud-starter-sleuth 依赖:

1
2
3
4
5
6
7
8
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

除此之外还有 Spring cloud 全局配置,添加 sleuth 的配置项(在最新版本中无需添加),以及项目的 application.name 等:

1
2
3
4
5
6
7
8
9
spring:
application:
name: SleuthService
sleuth:
sampler:
probability: 0.3 # 设置抽样采集率为 30%,默认为 0.1 即 10%

server:
port: 8210

Application.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.logging.Logger;


/**
* 实现日志采样(msg echo log?)
*
* @author kunlun
* @date 2021/7/24
*/
@SpringBootApplication
@RestController
public class DemoApplication {

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

private static final Logger LOG = Logger.getLogger(DemoApplication.class.getName());

@GetMapping("/hey")
public String hey() {
LOG.info("return /hey view");
return "hello";
}
}

其中 Logger 主要的作用就是将信息输出到控制台上,也就是通过 @GetMapping 进行请求所返回的相关信息,当然你也可以配合服务集群来实现服务消费者的作用。当访问 http://localhost:8210/hey 接口后,控制台输出格式为 [应用名称,Trace id,Span id] 直观点的就是 SleuthService,9d1a88683cbacd11,9d1a88683cbacd11

如果启动后没有关于 Sleuth 相关的信息,那么可以通过 application.yml 全局配置文件中添加日志输出为 DEBUG 类型看看:

1
2
3
logging:
level:
ROOT: DEBUG

Zipkin

Zipkin 同样是基于 Google Dapper 由 Twitter 所开发的且已经被 Spring cloud 良好的集成,你可以理解为他是 Sleuth 的数据可视化版本,并且通过 Annotation 来展示服务的相关信息,但首先你需要下载 Zipkin 的控制台并运行:

1
2
curl -sSL https://zipkin.io/quickstart.sh | bash -s
java -jar zipkin.jar

同时它还支持 Docker、从源代码运行等方式来启动,对于我来说通过 Jar 方式来执行已经非常便捷了,最坏的结果就是你需要先 clone 一下,然后在编译一下,之后在设置为 bin 一下,然后出错了又找一下,简单多了。

启动完后最为主要的是 http://127.0.0.1:9411/ 这也是 Zipkin 的默认端口和地址,记录下来之后在项目中进行绑定并添加依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-zipkin -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
<version>2.2.8.RELEASE</version>
</dependency>

实际上主要是 spring-cloud-starter-zipkin ,至于其他的只是我测试环境需要用到的,当然 spring-boot-starter-web 是可以不用加的,如果加了的话需要在配置文件中声明 main-web-application-type=reactive 来保证服务的正常启动(Gateway 已经存在了 web)

Application.yml

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
spring:
application:
name: Zipkin
cloud:
gateway:
routes:
- id: hey
uri: lb://service-provider/hey
predicates:
- Path=/hey
discovery:
locator:
# 允许服务发现
enabled: true
consul:
host: localhost
port: 8500
discovery:
service-name: service-provider
zipkin:
base-url: http://localhost:9411
sender:
type: web
main:
web-application-type: reactive
server:
port: 8210

之后请求相应的接口并重新在 Zipkin 控制台上点击 RUN QUERY 刷新下请求列表即可获取当前服务的响应以及依赖关系等更加详细的视图,读者可根据自身需要来进行查阅,但唯一不好的就是 Zipkin 不是异步刷新,需要手动点……,以及在查看详细请求数据的时候界面没有返回按钮(也许设计起初是为了靠浏览器自带的 <- -> 来实现窗口的返回)。

系统架构发展

单体应用


在互联网的发展初期,用户数量少而造成的一般站点流量也非常的少,但服务器等硬件产品的价格较高。因此一般的开发者会选择将 站点的所有功能 集成在一起,来进行开发,这就是 单体应用。随着单体应用的发展,也出现了 MVC、MVP、MVVM等开发单体应用的程序依然可以满足业务的需求。

优缺点

而单体应用的有优点有

  1. 易于开发、测试、管理、部署
  2. 想对于微服务中可以避免开发

应用缺点

  1. 团队合作困难
  2. 代码的维护的较为无语
  3. 项目的重构很复杂
  4. 对项目的可扩展读有很大的阻碍
  5. 随着项目的资源越来越大,code 越来越多,会造成编译时的速率。

为了解决这个问题,随着访问量的不断增大,数据访问框架 ORM的工作量过载导致数据库崩溃。单体应用可以根据集群来进行应对,由于他的缺点需要对之前的单体应用进行一定的拆分,因此 垂直应用 就出现了人们的视野中。

垂直应用


为了解决上述单体应用随着访问量的不断增大,因此单体应用只能通过集群来进行解决和应对,而在此过程中衍生了垂直应用。垂直应用可以根据业务的需求来解决单体应用的缺点。

垂直应用即 使用分层设计的开发应用,也随着系统复杂度的增加,系统架构的变化和侧重点均不一样。之前介绍的单体应用 当网站流量小的时候,只需要一个应用将所有功能部署一起,来减少部署的节点和成本,而此时的 数据访问框架(ORM) 的工作量多少,成为了服务器是否崩溃的关键。

垂直应用为了解决这个问题,将单一用用拆分成互不干扰的多个应用,来提升效率,此时用于加速前端开发的 MVC 框架成为关键。我们将一个单体应用拆分成了三个单体应用,这样可以 解决高并发,资源分配的问题,并提高项目的扩展、容错性以及资源分配等

水平拆分

垂直应用的拆分可以分为 水平拆分 以及 垂直拆分,其中说平拆分主要是指根据 所属业务也进行划分。但是水平拆分会造成存在 重复造轮子 的问题,且难于维护,而优点是业务拆分后的互相影响较小。

垂直拆分

所谓垂直拆分,即根据按照 系统 来进行拆分,因此 Auth 可以被拆分为 登录和注册。垂直拆分的有点是可以按照分配资源和流量,以及垂直调用之间不会进行影响,但缺点是完全存在 重复造轮子 的问题。

分布式应用应用系统


分布式应用系统是由 若干个独立的计算机集合 ,而成的分布式应用系统。简单概述下就是可以将此堪称两个服务分别运行在两台主机中,他们的相互协作来最终完成一个功能或服务,从理论中来讲这将会被称之为 分布式系统

当然,在上述中我们讲到了垂直应用,如果是相同的程序,将会被称之为 集群,通过不断的 横向扩展 来提升服务能力。

关于横向和纵向扩展

横向和纵向扩展,也有人称之为水平扩展和垂直扩展,我们可以假设下当有一台服务器,每天经历了几千次请求天天崩,因此你需要提升服务器性能。此时 横向扩展就是多增加几台服务器一起进行服务,而 纵向扩展 则是将该服务器换成性能更好的服务器。

服务治理


随着服务数量的不断增加,服务中的资源浪费和调度等问题也会随之而来,此时服务治理(Service-Oriented Architecture (SOA) governance)的作用就起到了一个可以基于访问压力来实时管理集群的容量,从而提高集群的利用率。

为什么要使用 SOA?

  1. SOA 在市场中得到了广泛的使用,可以快速的响应并根据情况作出有效的更改
  2. SOA 解决了 服务间的通信问题 ,通过引入 ESB、技术规范、服务管理规范来解决不同服务之间的通信问题
  3. SOA 解决了 基础服务的梳理 问题,以 SEB 为中心树立和规整了分布式服务
  4. SOA 解决了 业务服务化的问题,将业务为驱动,将业务封装到服务中。
  5. SOA 解决了 服务可复用 的问题,将业务功能设置为通用的业务服务,来实现业务的逻辑复用。

ESB

企业服务总线(ESB,The Enterpries Service Bus)可以将类似总线的基础设施将所有服务连接在一起。并作为服务治理中的通信中心,允许连接多个系统、应用和数据,并无终端地址连接多个系统。

微服务


需要庆幸的是,微服务(Microservices)采用的是服务治理中心,而不是在 SOA 架构中的中心话 ESB,因此服务的调度不需要知道 服务提供者的IP地址、端口号等,住需要知道在服务中心 注册了的服务名即可

微服务指的是将 系统业务功能划分为极小的独立微服务,每个微服务只关注于某个完成的一个小任务。因此系统中的单个微服务可以被独立部署和扩展,在上图中,Provider 1 和 Provider2 在 服务中心(Eureka Server) 注册微服务来让 服务消费者(Service Consumer)进行调用服务(Remote Call)

服务网格


服务网格(Service Mesh)独立与服务之外运行,是服务间通信的基础设施层,可以将它比作是应用程序或微服务之间的 TCP/IP,负责服务之间的网格调用、限流、融断以及监控等。服务网格由 数据平台(Data Plane)和 控制平台(Control Plane) 组成,服务与服务之间通过 边车(Sidecar) 来进行通信,所有边车和网络链接就形成了 服务网格(Service Mesh),其中 Sidecar 主要负责服务发现和容错处理

数据平台与控制平台

数据平台主要用于 处理服务之间的通信,并实现服务的发现,复杂均衡、流量管理、健康检查等。而控制平台主要用于 管理并配置边车(Sidecar)来执行策略和搜集数据

在正常的情况下应用程序的开发人员并不会关系 TCP/IP层,这在服务网格时也依然适用,开发人员也不需要关心服务的融断、断流、限流、监控等,因为这些都将由服务网格处理

特点与区别

一是服务网格的 侧重点 不同,为服务架构更关注服务之间的 生态(如服务治理 SOA) ,而服务网格则更关注服务之间的 通信
二是 侵入性不同,微服务架构实现了架构间分离出来解决问题(解藕),而服务网格则实现了服务框架与服务之间分离出来解决问题。需要值得注意的是服务网格是服务之外独立运行的模块,他提供了微服务框架功能,但不需要在代码和配置中添加相应的依赖库和依赖配置项。

服务网格的特点有:

  1. 对服务没有侵入性
  2. 是一个应用程序之间的中间层
  3. 一个轻量级的网络代理
  4. 应用程序对服务网格无感知
  5. 能够分离出来解决用用程序的重试、监控、追踪和服务发现等问题。

Spring boot and Thyemeaf 条件与循环

在众多语言之中,都有条件和循环等三个非常重要的语句分别为if、switch、while等三个语句,分别根据不同场景而运用,本次我们将会一一进行列举,也作为我们的Thymeleaf结尾篇(因为我们主打的还是Spring boot,不是Thymeleaf,Thymeleaf只是作为课外部分)

if

布尔也是开发语言三件套之一,通常布尔用于判断和各种循环条件之中,本书将会使用Thymeleaf模板引擎来实现布尔的一系列操作:

在Thymeleaf模板引擎之中,布尔的写法是有区别的如**<div th:if=“${data} == ‘thymeleaf’”<div th:if=”${data=’Thymeleaf’”**,两种写法其中第一种写法即布尔在大阔号外的即将会由Thymeleaf负责处理,如果将布尔写在大阔号内将会由OGNL/SpringEL引擎负责处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Home</title>
<link th:href="@{/css/style.css}" href="../static/css/style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<h5>Java Thymeleaf boolean</h5>
<hr />
<div th:if="${data}=='Thymeleaf'">
<p>Thymeleaf....if $data 变量正确</p>
</div>
</body>
</html>

if and unless

本书通过if and unless来实现判断data变量是否为空作为案例,如data变量为空则输出不为空等信息,实现方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Home</title>
<link th:href="@{/css/style.css}" href="../static/css/style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<h5>Java Thymeleaf boolean</h5>
<hr />
<div th:if="${data} == true">
<p>The true urlController $data</p>
</div>
<div th:unless="${data} == true">
<p>The false urlController $data</p>
</div>

</body>
</html>

在Thymeleaf之中,th:if与th:unless的意思和功能是不同的,通常为th:is是条件成立时执行的内容而th:unless则是条件不成立时执行的内容

switch

switch语法表达式可有效的等效于if或是unless等Thymeleaf语法表达式,来有效且有条件的显示某一条内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- index.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Home</title>
<link th:href="@{/css/style.css}" href="../static/css/style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<h5>Java Thymeleaf switch</h5>
<hr />
<div th:switch="${data.name}">
<p th:case="'kunlunsiqu'">My name is kunlunsiqu</p>
<p th:case="'kun'">My name is kun</p>
</div>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// urlController.java
package com.example.demo;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class urlController {
@GetMapping("index")
public String getindex(Model model) {
data dataone = new data("kun",20);
model.addAttribute("data",dataone);
return "index";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// data.java
package com.example.demo;

public class data {
private String name;
private int age;

public data(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}

switch + case

上面我们介绍了switch单个的执行效果,当然我们虽然也写了 th:case但如果条件都不构成时我们需要输出什么呢?这个问题在Thymeleaf已经给了我们答案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- index.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Home</title>
<link th:href="@{/css/style.css}" href="../static/css/style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<h5>Java Thymeleaf switch</h5>
<hr />
<div th:switch="${data.name}">
<p th:case="'kunlunsiqu'">My name is kunlunsiqu</p>
<p th:case="'kun'">My name is kun</p>
<p th:case="*">My name is no kunlunsiqu and kun!</p>
</div>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// urlController.java
package com.example.demo;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class urlController {
@GetMapping("index")
public String getindex(Model model) {
data dataone = new data("jujingyi",20);
model.addAttribute("data",dataone);
return "index";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// data.java
package com.example.demo;

public class data {
private String name;
private int age;

public data(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}

switch与case相互使用的时候本身并不会对当条件都不构成时输出信息,而如果将case条件更改为"**"时将会在当条件都不构成时输出指定信息,当然,不仅仅可以使用case来作为当条件都不构成时输出的信息,通过阅读上一章节读者们可会有更多的方案来代替 th:case=”*” 语法。

each

本书我们通过使用列表来进行循环的实现,其主要由Controller(控制器)和th:text、th:each来分别进行实现如:

WebController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.example.demo;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Arrays;
import java.util.Map;

@Controller
public class WebController {
@RequestMapping("/index")
public String index(Map<String,Object> map) {
// Map&map
map.put("data",Arrays.asList("one","two","three"));
return "index";
}
}

index.html

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Index</title>
</head>
<body>
<h5>Java Thymeleaf th:each</h5>
<hr/>
<p th:text="${each}" th:each="each: ${data}"></p>
</body>
</html>

application.propertion

1
spring.thymeleaf.cache=false

Spring Boot and Thymeleaf 常量与文字处理(Literals and Text operations)

Literals

在Thymeleaf中,我们将Literals翻译为类型,即Thymeleaf中和其他语言一样,共包含了字符串、数字、 布尔、操作符等四种数据类型,本章将会对这四种数据类型做出实例及相关解释和实现。

字符串

字符串即你输入的任何字母都叫字符串类型,英文即 String,代表着字符串类型,按道理讲常见的各种字母和数字都可被字符串所海纳。

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Home</title>
<link th:href="@{/css/style.css}" href="../static/css/style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<h5>Java Thymeleaf String</h5>
<hr />
<p>Hello,world<span th:text="'The on html+thymeleaf file'">The one html file</span></p>
</body>
</html>

之所以上图输出的文字是红色的,也许可能是因为 /css/style.css的原因,读者可自行进行编写,如你可以编写一个 :

p {color: green}

让生活多一点乐趣与对生活更或者世界的向往,看开一切,展开心扉,无所畏惧,你将变得更加美好。

String + var

Thymeleaf还支持数据类型与变量格式化的内容,即在一个""下输出信息,这个支持方便了很多,支持的也很多,语法为:

index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Home</title>
<link th:href="@{/css/style.css}" href="../static/css/style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<h5>Java Thymeleaf Literals</h5>
<hr />
<p th:text="|Welcome to Thymeleaf, ${data}!|"></p>
</body>
</html>
urlController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.example.demo;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class urlController {
@GetMapping("index")
public String getindex(Model model) {
model.addAttribute("data","Thymeleaf");
return "index";
}
}

数字

数字(int),即0~9个数字组合的一种数据,这种你可以随便组合比如7758258这些,当然我也不用太过多的介绍,我相信读者们都有一定的数据类型基础,毕竟学各种语言的时候都会学习到数据类型,本次我们将会使用Thylemeaf来实现一个数字类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Home</title>
<link th:href="@{/css/style.css}" href="../static/css/style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<h5>Java Thymeleaf String</h5>
<hr />
<p>Hello,world <span th:text="2020">The one html file</span></p>
</body>
</html>

算数运算符

在众多语言之中,算数运算符和数据类型一样,都是一个必经之路,所以本文不做过多演示,刚好今天是2021年1月6日,晚 01:18:07 点,就利用算数运算符祝大家2021年新年快乐吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Home</title>
<link th:href="@{/css/style.css}" href="../static/css/style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<h5>Java Thymeleaf String</h5>
<hr />
<p>Hello,world <span th:text="2020 + 1">The one html file</span></p>
</body>
</html>

String + 算数运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Home</title>
<link th:href="@{/css/style.css}" href="../static/css/style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<h5>Java Thymeleaf Literals</h5>
<hr />
<p th:text="'Hello,' + ${data} + '!'">The one html file</p>
</body>
</html>

在Thymeleaf模板引擎之中,我们可以通过数据类型和算数运算符进行叠加(虽然用的是字符串类型),通过算数运算符将数据类型与变量相加将会得到组合后的结果。

布尔

布尔也是开发语言三件套之一,通常布尔用于判断和各种循环条件之中,本书将会使用Thymeleaf模板引擎来实现布尔的一系列操作:

在Thymeleaf模板引擎之中,布尔的写法是有区别的如**<div th:if=“${data} == ‘thymeleaf’”<div th:if=”${data=’Thymeleaf’”**,两种写法其中第一种写法即布尔在大阔号外的即将会由Thymeleaf负责处理,如果将布尔写在大阔号内将会由OGNL/SpringEL引擎负责处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Home</title>
<link th:href="@{/css/style.css}" href="../static/css/style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<h5>Java Thymeleaf boolean</h5>
<hr />
<div th:if="${data}=='Thymeleaf'">
<p>Thymeleaf....if $data 变量正确</p>
</div>
</body>
</html>

if and unless

本书通过if and unless来实现判断data变量是否为空作为案例,如data变量为空则输出不为空等信息,实现方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Home</title>
<link th:href="@{/css/style.css}" href="../static/css/style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<h5>Java Thymeleaf boolean</h5>
<hr />
<div th:if="${data} == true">
<p>The true urlController $data</p>
</div>
<div th:unless="${data} == true">
<p>The false urlController $data</p>
</div>

</body>
</html>

在Thymeleaf之中,th:if与th:unless的意思和功能是不同的,通常为th:is是条件成立时执行的内容而th:unless则是条件不成立时执行的内容

Text operations

算数运算符

算数运算符之中,主要包含和使用+、-、*、/、%等算数符号,在此的过程之中,将会由OGNL引擎来代替Thymeleaf标准表达式引擎执行,显然这也是另一个写法,通常算数运算符包含在大阔号中的将会由OGNL引擎代替,而写在大阔号外的则由Thymeleaf引擎执 ,算数运算符和其他开发语言中的相差无几,本书将不详述,有兴趣的读者可自行了解

比较器

在Thymeleaf之中,比较器和上述的算数运算符不同,比较器的写法可能与其他开发语言不同,如:

ID DA
> gt
< it
>= ge
<= le
! not
== eq
!= neq/ne

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Home</title>
<link th:href="@{/css/style.css}" href="../static/css/style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<h5>Java Thymeleaf comparator</h5>
<hr />
<div th:if="${data} eq 'Thymeleaf'">
<p>urlController comparator eq(==) Thymeleaf</p>
</div>
</body>
</html>

默认表达式

在Thymeleaf之中,与其说是默认表达式不如说是 if……else的在称,其意思为,如当前指定的变量与条件不符则默认输出指定信息或变量,这个很类似于else :

urlController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.example.demo;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class urlController {
@GetMapping("index")
public String getindex(Model model) {
data dataone = new data(null ,20);
model.addAttribute("data",dataone);
return "index";
}
}
data.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.example.demo;

public class data {
private String name;
private int age;

public data(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Home</title>
<link th:href="@{/css/style.css}" href="../static/css/style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<h5>Java Thymeleaf Elvis</h5>
<hr />
<div th:object="${data}">
<p>Name: <span th:text="*{name}?: '(No name? Are you ok?)'"></span></p>
</div>
</body>
</html>

原型文本默认表达式

原型文本默认表达式在Thymeleaf官方文档中的名称叫做(The No-Operation token)即“禁止操作令牌”,其主要由令牌下划线符号"_"来表示,这样看起来代码简洁而美丽且大方(虽然IDE开发环境中会报错,但依然好看):

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Home</title>
<link th:href="@{/css/style.css}" href="../static/css/style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<h5>Java Thymeleaf Elvis</h5>
<hr />
<div th:object="${data}">
<p>Name: <span th:text="*{name} ?: _">No name?Are you ok?</span></p>
</div>
</body>
</html>

这样的写法即约等于了 <p>Name: <span th:text="*{name}?: '(No name? Are you ok?)'"></span></p>即默认表达式,使用原型文本默认表达式不仅代码美观且大气,还显得格外诱人。

数据转换与格式化

Thymeleaf提供数据转换与格式化的表达式,通过数据的转换来让其变成字符串类型(String),无论变量数据是什么格式,都将被转化为字符串类型,本书例子我们会将数字类型转换为字符串类型:

inde.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Home</title>
<link th:href="@{/css/style.css}" href="../static/css/style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<h5>Java Thymeleaf The No-Operation token</h5>
<hr />
<td th:text="${{data.age}}"></td>
</div>
</body>
</html>

原本的data.age本是数字类型,但通过两个大阔号即{{……}}可让其自动转换/格式化为字符串类型。

Spring boot and Thymeleaf 变量表达式

标准变量表达式语法

ID DA FA
${……} 变量表达式 变量表达式
*{……} 选择变量表达式
#{……} 信息表达式
@{……} 链接URL表达式
~{……} 片段表达式

在此我们只线上简单表达式的语法,还有一些Thymeleaf的语法我相信读者都是学过html、css、js这些的都可以大致看懂,所以本书也没必要将重点列在表达式语法之中,在此基础上学习和认知、了解到简单表达式就差不多了。

标签

ID DA
th:id 替换id
th:text 文本替换
th:utext 不可被替换的文本
th:object 替换对象
th:value 替换值
th:each 迭代
th:href 替换超链接
th:src 资源替换

Thymeleaf ${……}

Thymeleaf ${……}的主要作用是变量表达式,通常我们需要另建一个Java文件在spring boot web项目之中,然后html页面通过${……}来获取.Java文件的内容。有一个有趣的故事是单个打开html和在服务器端显示的不同,这里的不同主要是html不懂Thymeleaf语法,于是就输出了${……}之后标签内的语法如:<p th:text="${name}">Go html</p>在服务器端会输出.Java文件内name相应的数据内容,而在html环境下由于html也不懂Thymeleaf语法干脆忽略掉了而输出Go html

application.properties
1
2
spring.thymeleaf.cache=false
spring.thymeleaf.encoding=UTF-8
urlController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.example.demo;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class urlController {
@GetMapping("index")
public String getindex(Model model) {
model.addAttribute("name","Thymeleaf");
return "index";
}
}
index.html
1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Home</title>
</head>
<body>
Thymeleaf one html
<p th:text="${name}">Go html</p>
</body>
</html>

需要值得注意的是在urlController中所属的包是由spring boot项目构建时自动生成的包,直接在com.example.demo包下新建类即可,而index.html文件是存放于src/main/resources/templates之中的,在application.properties内的配置分别为自动清除Thymeleaf缓存,即不需要重启Spring boot来刷新数据,理论上是如thymeleaf改变服务端也随即改变,这个改变是不包括css/js的。

${……[]}

在Spring boot与Thymeleaf搭配的项目之中,可以使用bash方法编写一个类似于列表数据的方法,及通过java建立对象,html使用${……[]}的方法引入java对象数据即:

data.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.example.demo;

public class data {
private String name;
private int age;

public data(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
urlController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.example.demo;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class urlController {
@GetMapping("index")
public String getindex(Model model) {
data dataone = new data("username",20);
model.addAttribute("namedata",dataone);
return "index";
}
}
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Home</title>
<link th:href="@{/css/style.css}" href="../static/css/style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<h5>Java Bean object</h5>
<table>
<tr>
<td>Name:</td>
<td th:text="${namedata.name}">namedata</td>
</tr>
<tr>
<td>Age:</td>
<td th:text="${namedata.age}">agedata</td>
</tr>
</table>
</body>
</html>

Thymeleaf @{……}

本文的@{……}由于使用本地环境导致spring boot无法找到style.css,这个问题其实主要的原因是环境的不同,在spring boot是自动配置、自动寻找的,所以不要使用绝对目录如:

1
2
3
4
5
6
7
8
9
10
11
12
 <!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Home</title>
<link th:href="@{../static/css/style.css}" href="../static/css/style.css" rel="stylesheet" type="text/css" />
</head>
<body>
Thymeleaf one html
<p th:text="${name}">Go html</p>
</body>
</html>
index.html
1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Home</title>
<link th:href="@{/css/style.css}" href="../static/css/style.css" rel="stylesheet" type="text/css" />
</head>
<body>
Thymeleaf one html
<p th:text="${name}">Go html</p>
</body>
</html>
style.css
1
2
3
4
# resources/static/css/
p {
color: red;
}

你会发现直接点击html并浏览时css是正常运行的,而正到服务器端访问时就出现大问题了,就是访问不到style.css,返回结果404。这时我们将th:href="@{../static/css/style.css"更改为th:href="@{/css/style.css}即可,这样即保证了服务端的正常渲染也保证了静态调试的时候正常渲染。

th:href @{……}

本次我们使用 th:href 标签来实现 @{……}方法,通常我们在生产环境之中都需要用到相对路径即 https://pv.zsun.org.cn/about,而不是绝对路径https://pv.zsun.org.cn/login.php,这样不仅仅链接不美观还存在着一些```安全风险```,在互联网中有一群打着“白帽子”的脚本小子通常最喜欢这种使用绝对路径的站点了,他们会不择手段的对你的站点进行测试,这种感觉也非常无语,所以本章通过 th:href来实现相对目录。

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Home</title>
<link th:href="@{/css/style.css}" href="../static/css/style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<h5>Java Thymeleaf href</h5>
<hr />
<a href="insert.html" th:href="@{insert}">Go insert</a>
</body>
</html>

这样写的好处是不仅仅是为了安全性考虑,且在本地测试中不会在对服务器应用进行设置,无需另开发对生产环境的程序或工作,一次开发,兼顾了本地测试和生产环境,这也是Thymeleaf的特点之一。

Thymeleaf *{……} & ${……}

在Thymeleaf标准表达式语法之中,可使用*{……}${……}互相进行搭配,其中可更方便的运用在列表之中,而实现过程与Thymeleaf中的 ${……[]}相差无几,只是多了个*{……}让其更加方便的编写:

data.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.example.demo;

public class data {
private String name;
private int age;

public data(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
urlController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.example.demo;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class urlController {
@GetMapping("index")
public String getindex(Model model) {
data dataone = new data("username",20);
model.addAttribute("name","Thymeleaf");
model.addAttribute("namedata",dataone);
return "index";
}
}
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Home</title>
<link th:href="@{/css/style.css}" href="../static/css/style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<h5>Java Bean object</h5>
<table th:object="${namedata}">
<tr>
<td>Name:</td>
<td th:text="*{name}">namedata</td>
</tr>
<tr>
<td>Age:</td>
<td th:text="*{age}">agedata</td>
</tr>
</table>
</body>
</html>

Thymeleaf #{……}

与上面的一些表达式语法相比,基本上都是通过读取Controller控制器值来读取信息的,而相比之下 #{……}就显得平淡无奇且低调奢华了,这个表达式虽然并不常用但却比通过读取Controller值方便许多,因为他本身就是用于读取配置内的信息的(这里的配置仅单指.properties)Springboot配置文件。

application.properties
1
2
3
spring.thymeleaf.cache=false
spring.thymeleaf.encoding=UTF-8
spring.messages.basename=templates/data

application 主要用于指定创建好的.properties文件,之后Thymeleaf #{……}通过读取.properties配置文件信息来显示输出,如不再spring boot全局配置文件中配置则会显示 ??language_zh_CN??,所以需要指定其.properties目录和文件名

data.properties
1
2
data.name=kunlunsiqu
data.age=22
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Home</title>
<link th:href="@{/css/style.css}" href="../static/css/style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<h5>Java Bean object</h5>
<table>
<tr>
<td>Name:</td>
<td th:text="#{data.name}">namedata</td>
</tr>
<tr>
<td>Age:</td>
<td th:text="#{data.age}">agedata</td>
</tr>
</table>
</body>
</html>

Thymeleaf ~{……}

Thymeleaf {……}片段表达式通过th:insert标签分别分为两步为分别为定义和引入两种方式,可以理解为一个页面中的数据通过片段表达式可供多个页面进行引用,其格式为```th:insert=”{文件名 :: 片段名}```”如本书例子;

定义
insert.html
1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert</title>
</head>
<body>
<div th:fragment="data">
2020-01-05 19:48:34 Jiangxue & ZhongShan
</div>
</body>
</html>
引入
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Home</title>
<link th:href="@{/css/style.css}" href="../static/css/style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<h5>Java Insert</h5>
<hr />
<div th:insert="~{insert :: data}"></div>
</body>
</html>

Spring boot Web and Thymeleaf 开发环境构建

在本文之中,我们主要使用spring tool suite来构建spring boot 的web开发环境,通过Eclipse集成开发环境中选择New->Other->Spring Boot->Spring Starter Project 下来构建Spring web项目,通过Spring tool suite远程访问https://start.sring.io/来构建spring boot应用(```这个过程虽然是gui形式但是依然需要联网,否则无法从start.spring.io中获取spring boot包),通过Spring tool suite图形化构建spring boot开发环境。

在spring boot 版本之中,需要对创建完后的spring boot项目中的pom.xml文件进行编辑添加其web支持,而spring boot非常的遵守开箱即用少配置的原则自动帮我们对在项目根木目录下的文件pom.xml添加了spring boot web支持:

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

Thymeleaf 创建

Thymeleaf 和 FreeMarker一样,都是一款模板引擎,而与之不同的是Thymeleaf是适用于独立环境和现代服务器的模板引擎,并不是说FreeMarker不好,而是说Thymeleaf是专门适用与Java的,并对Spring MVC提供了完美的支持。除此之外,Spring boot的Web应用还支持包括 FreeMarker,Thymeleaf,Groovy,Velocity、Mustache等模板引擎。

在本文之中我们主要介绍Thymeleaf的特点及概括,Thymeleaf的主要目标和工作居士为当前开发的项目中的代码格式带来优雅,自然的HTML,并可在浏览器中正常现实,并作为静态原型工作,从而可以加强团队之间的协作。Thymeleaf 的运行方式有两种,一种为静态一种为动态,读者在学习Thymeleaf之中可以深刻的理会到这个原理,虽然心情波折但是依然相差无几。

Spring boot Thymeleaf 创建

在spring boot 5 中,WebFlux的出现对Web应用不再唯一,所以spring-boot-starter-thymeleaf依赖将不会出包含在spring-boot-starter-web模块中,所以需要开发者手动在开集成开发环境中进行添加和构建,说的通俗易懂点就是在New Spring Starter Project -> New Spring Starter Project Dependencies [Spring Web and Thymeleaf]即可,在New Spring Starter Project Dependencies中选择或搜索Spring Web 、Thymeleaf并勾选然后创建即可。

Themeleaf 目录

在创建spring boot web开发环境后,通常会生成关于Thymeleaf文件的目录在src/main/resources下如:static、templates其中static通常用于存放js、css、img等,而在templates目录下主要用于存放视图页面。

输出

通过在 src/main/java/中创建软件包为com.example.controller的控制器类:

src/main/java/

TestThymeleafController.java
1
2
3
4
5
6
7
8
9
10
11
12
package com.example.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class TestThymeleafController {
@RequestMapping("/")
public String test() {
return "index";
}
}

src/main/resource/templates

index.html
1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello,Spring boot</title>
</head>
<body>
Test Spring boot The Thymeleaf
</body>
</html>

Thymeleaf Eclipse 引入

在默认的情况下,Eclipse是不支持Thymeleaf语法的,所以我们需要手动添加eclipse即集成开发环境对Thymeleaf语法的支持和提供代码提示等功能,通常在没有代码提示的时候开发Thymeleaf是一件非常无语的事情,比写spring mvc还无语,安装Thymmeleaf插件的方法有很多,如通过远程和离线进行安装,在eclipse开发环境中选择 Help->Install New Software……,中可添加url和本地进行安装

远程下载

在 install New Software……中的url处填写 http://www.thymeleaf.org/eclipse-plugin-update-site/,然后在name处输入Thymeleaf之后通过一系列下一步完成同意协议操作即可。

离线下载

离线下载即在你有网的时候下,然后安装时是不需要网络的,其方法和远程下载无几,https://github.com/thymeleaf/thymeleaf-extras-eclipse-plugin 在此处进行下载,然后将下载的zip包按照远程下载的方式在 Install New Software…… -> Add ->url 处替换为你下载的zip文件即可,然后步骤和远程下载相差无几。

Spring boot Thymeleaf 刷新

在开发Thymeleaf之中会遇到一个非常无语的问题,就是说当更改完Thymeleaf时,需要手动重启Spring boot服务,而Spring boot服务又需要调用Tomcat服务应用,会造成一定的消耗,所以我们本次以IDEA环境为例(不要问我为什么之前用Eclipse,现在转Idea,满满的你就会知道了

application.properties

application.properties 文件一般存储在 src/main/resources/目录下,无论在eclipse或idea环境中他都是一片叶子,我们在其配置:

1
spring.thymeleaf.cache=false

即关闭spring boot right thymeleaf的缓存,这样更新Thymeleaf文件后在其本地环境就会变得不一样了

Idea Build

如果你是Idea 环境那就是另一种福音了,我们可以通过Idea集成开发环境进行一定的配置如:
File->Settings->Build,Execution,Deployment->Compiler
标签内将Build project automatically勾选,即“自动构建项目”.,然后点击应用(Apply)即可

Maintenance and Registry

使用快捷键 Shift+Ctrl+Alt+/即可唤出Maintenance页面,然好点击Registry……标签将key为compiler.automake.allow.when.app,running在Value中打个勾之后关闭即可,然后重启Idea。

Spring Boot 条件与其实现

我们可以通过直接阅读引入org.springframework.boot.autoconfigure.aop.AopAutoConfiguration;的Aop包下的AopAutoConfiguration.class来了解到其Spring boot自动配置的实现过程,而AopAutoConfigutaion.class中的关键代码如下:

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
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Advice.class)
static class AspectJAutoProxyingConfiguration {

@Configuration(proxyBeanMethods = false)
@EnableAspectJAutoProxy(proxyTargetClass = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false",
matchIfMissing = false)
static class JdkDynamicAutoProxyConfiguration {

}

@Configuration(proxyBeanMethods = false)
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
matchIfMissing = true)
static class CglibAutoProxyConfiguration {

}

}

通过AopAutoConfiguration.class的代码中我们可以理解到,基本上Spring Boot中的自动配置实现过程是通过@Conditational而@ConditationalOnProperty(即条件属性,单Conditational指“有条件的”)注解实现的,我们可通过Java Doc的官方文档中读取和了解到有关@ConditationalOnPropertry注解相关的信息

1.1. 条件注解

Spring的条件注解说白了就是程序的配置类和配置项,即满足特定了条件才会被调用,这就和if和try的条件判断语句一样,if的存在是判断是否等于或不等于某个条件,而try的存在则是判断这段code是否出现异常,出现异常后又执行什么方法或调用什么类等,在这里Spirng条件注解同样的意思。
spring-boot-autoconfigure-2.4.1.jarspring boot核心依赖包下,有38个条件注解类如下:

ID DA FA
@ConditionalOnBean OnBeanCodition.class Spring存在指定的实例Bean
@ConditionalOnClass OnClassCondition.clss 类加载器中存在对应的类
@ConditionalOnCloudPlatform OnCloudPlatformCondition.class 类中是否存在云平台
@ConditionalOnExpression OnExpressionCondition.clss 判断SpEL表达式是否成立
@ConditionalOnJava OnJavaCondition.class Java版本是否符合你的条件要求
@ConditionalOnJndi OnJndiCondition.class 在JNDI(即Java命名项目和目录接口)条件查找指定位置
@ConditionalOnMissingBean OnBeanCondition.class Spring容器中不存在指定的实例
@ConditionalOnMissingClass OnClassCondition.class 类加载器中不存在对应的类
@ConditionalOnNotWebApplication OnWebApplicationCondition.class 当前应用程序不是Web程序应用
@ConditionalOnProperty OnResourceCondition.class 应用环境中实行是否存在指定的值
@ConditionalOnResource OnResourceCondition.class 是否存在指定的资源文件
@ConditionalOnSingleCandidate OnBeanCondition.class Spring容器中是否存在且只存在一个对应的实例Bean
@ConditionalOnWebApplication OnWebApplicationCondition.class 当前应用程序是Web程序

通过阅读以上列出的相关注解和注解实现类我们可随即查看实现类的过程,而本次我们主要阅读```OnWebApplicationCondition.class``即@ConditionalOnWebApplication的注解实现方法以及原理。

1.1.1. OnWebApplicationCondition.class

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

// 匹配Web应用
@Override
protected ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
AutoConfigurationMetadata autoConfigurationMetadata) {
ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
for (int i = 0; i < outcomes.length; i++) {
String autoConfigurationClass = autoConfigurationClasses[i];
if (autoConfigurationClass != null) {
outcomes[i] = getOutcome(
autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnWebApplication"));
}
}
return outcomes;
}

// 确定匹配结果并以日志的形式输出
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 检查是否使用@OnWebApplicationCndition注解
boolean required = metadata.isAnnotated(ConditionalOnWebApplication.class.getName());
// isWebApplication 检测是否是Web 应用程序
ConditionOutcome outcome = isWebApplication(context, metadata, required);
// 如果有注解但不是Web应用环境则返回true
if (required && !outcome.isMatch()) {
return ConditionOutcome.noMatch(outcome.getConditionMessage());
}
// 如果没有注解但是web应用环境则返回true
if (!required && outcome.isMatch()) {
return ConditionOutcome.noMatch(outcome.getConditionMessage());
}
// 如果使用了@ConditionalOnWebApplication注解并是Web应用则返回匹配
return ConditionOutcome.match(outcome.getConditionMessage());
}

// 是否为Web应用环境
private ConditionOutcome isAnyWebApplication(ConditionContext context, boolean required) {
ConditionMessage.Builder message = ConditionMessage.forCondition(ConditionalOnWebApplication.class,
required ? "(required)" : "");
ConditionOutcome servletOutcome = isServletWebApplication(context);
if (servletOutcome.isMatch() && required) {
return new ConditionOutcome(servletOutcome.isMatch(), message.because(servletOutcome.getMessage()));
}
ConditionOutcome reactiveOutcome = isReactiveWebApplication(context);
if (reactiveOutcome.isMatch() && required) {
return new ConditionOutcome(reactiveOutcome.isMatch(), message.because(reactiveOutcome.getMessage()));
}
return new ConditionOutcome(servletOutcome.isMatch() || reactiveOutcome.isMatch(),
message.because(servletOutcome.getMessage()).append("and").append(reactiveOutcome.getMessage()));
}

自定义条件

条件实现类

MyCondition.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.example.demo;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;


public class MyCondition implements Condition {

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// TODO Auto-generated method stub
// 存在 test.properties
return context.getResourceLoader().getResource("classpath:test.properties").exists();
}
}

YourCondition.java

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.example.demo;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class YourCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// TODO Auto-generated method stub
// 不存在 test.properties
return !context.getResourceLoader().getResource("classpath:test.properties").exists();
}
}

Bean 接口实现类

MessagePrint.java

1
2
3
4
package com.example.demo;
public interface MessagePrint {
public String showMessage();
}

MyMessagePrint.java

1
2
3
4
5
6
7
8
package com.example.demo;

public class MyMessagePrint implements MessagePrint {
@Override
public String showMessage() {
return "test.properties 文件存在";
}
}

YourMessagePrint.java

1
2
3
4
5
6
7
8
9
package com.example.demo;

public class YourMessagePrint implements MessagePrint {
@Override
public String showMessage() {
// TODO Auto-generated method stub
return "test.properties 文件不存在";
}
}

配置类

ConditionConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.example.demo;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Conditional;

@Configuration
public class ConditionConfig {
@Bean
@Conditional(MyCondition.class)
public MyMessagePrint myMessage() {
return new MyMessagePrint();
}

@Bean
@Conditional(YourCondition.class)
public MessagePrint yourmessage() {
return new YourMessagePrint();
}
}

测试类

TestMain.java

1
2
3
4
5
6
7
8
9
10
11
12
package com.example.demo;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TestMain {
private static AnnotationConfigApplicationContext contextapplication;
public static void main (String args[]) {
contextapplication = new AnnotationConfigApplicationContext(ConditionConfig.class);
MessagePrint message = contextapplication.getBean(MessagePrint.class);
System.out.println(message.showMessage());
}
}

Spring Boot 读取与自动配置

读取

Environment

Environment是用于读取应用程序运行时环境变量的类,是可通过在application.properties中写入的key来通过Environment类读取的一种方式,而通过这种方式来读取的实现类通常称之为“Environment”类。

src/main/resources/application.properties

1
demo.msg = Hello I am here read config!

HelloWorldController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloWorldController {
@Autowired
private Environment env;
@RequestMapping("/demoEnv")
public String demoEnv() {
// 从 src/main/resources/application.properties 中的key读取配置文件数据
return "Spring boot:" + env.getProperty("demo.msg");
}
}

@Value

第二种方法就是使用@value注解来进行读取,与Environment的区别就是少了一行code,就是说用注解代替了Environment的return,来读取application.properties:

src/main/resources/application.properties

1
demo.msg = Hello I am here read config!

HelloWorldController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.example.demo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloWorldController {
@Value("${demo.msg}")
private String msg;
@RequestMapping("/demoValue")
public String demoValue() {
return "Spring boot" + msg;
}
}

@PropertySource

在前面所介绍的注解仅仅可用于读取application.properties的Spring boot全局配置文件,而如果想要读取另一个或其他的配置文件需要使用到@PropertySource + @Value来读取其他配置文件的内容,其步骤如下:

PropertySourceValueReaderOtherController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.example.demo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@PropertySource({"one.properties","two.properties"})
public class PropertySourceValueReaderOhterController {
@Value("${two.msg}")
private String twomsg;

@Value("${one.msg}")
private String onemsg;

@RequestMapping("/demoProperty")
public String demoProperty() {
return "one.properties:" + twomsg + "<br>" +
"two.properties:" + twomsg;
}

}

one.properties

1
one.msg = Hello

two.properties

1
two.msg = test PropertySource

@ConfigurationProperties

```@Value、@PropertySource```注解类似,通过使用该注解可以配置 Controller、Model 进行获取 ```application.properties``` 配置文件信息。
1
2
3
4
5
6
7
8

#### application.properties
```java
com.example.name=${name:sunlikun}
com.exmaple.age=16
com.example.address[0]=one
com.example.address[1]=two
com.example.address[2]=three

ConfigExampleModel

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
package com.example.demo.model;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.List;

/**
* ConfigExampleModel class
*
* @author sunlikun
* @date 2021/06/08
*/
@Data
@Component
@ConfigurationProperties(prefix = "com.example")
public class ConfigExampleModel {
/**
* @Data 自动生成 setter()、getter()、toString()、equals()、hashCode() 方法;
* @Component @Component 及表示为当时用该注解时,Spring 框架将会自动检测到这些类进行注入。通常表示说当前类不属于各种归类
* 时(如 Controller、Services)就可以通过使用该注解进行标注。
* @ConfigurationProperties 将配置信息自动封装为实体类,其 "prefix(前缀)"是配置文件中定义的配置信息: "com.example.{value}"
*/

private String name;
private int age;
private List<String> address;
}

ConfigExampleController

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

import com.example.demo.model.ConfigExampleModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
* ConfigExampleController class
*
* @author kunlun
* @date 2021/06/08
*/
@RestController
public class ConfigExampleController {
@Autowired
private ConfigExampleModel configExampleModel;

@RequestMapping("/")
public String getName() {
return configExampleModel.getName();
}

@RequestMapping("/address")
public List<String> getAddress() {
return configExampleModel.getAddress();
}
}

当一切完成之后我们访问 http://localhost:8080/address 会发现返回的是 ["one","two","three"],或者访问 http://localhost:8080/ 进行测试,此时返回的结果则是 sunlikun,而这正是 application.properties 下配置文件的内容数据。

日志

在众多的开发语言之中,日志是最为重要的一个功能,虽然这里说的开发语言不指java、c\c++、php这些,但是我们可拿mysql为例,在mysql中,日志是非常好的排错工具,通过阅读日志信息可帮助我们快速的了解错误和安全性的信息,学习spring boot自动配置之前,我们需要预先连接下spring boot的日志生成和管理以及对日志的配置。通常我们使用apache下的Log、LogFactory的方式来实现日志,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.example.demo;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class LogTestController {
private Log log = LogFactory.getLog(LogTestController.class);
@RequestMapping("/testLog")
public String testLog() {
log.info("test log");
return "test Log";
}
}

日志等级

在Spring boot中,日志等级和一些日志配置都是在application.properties文件下进行设置的,通常存储在src/main/resources目录下,而日志等级主要分为五个等级级别,以及三个颜色等级,共分别为:

ID DA FA
OFF 关闭 无颜色
FATAL 致命 红色
ERROR 错误 红色
WARN 警告 黄色
INFO 信息 绿色
DEBUG 调试 绿色
TREACE 追踪 绿色
ALL 所有 无颜色
通常我们可以使用针对默认和设置指定包下的日志等级两种方式为:

默认日志等级设置

1
2
# 设置日志的默认等级为 info
logging.level.root = info

指定包下的日志等级设置

1
2
# 设置 com.example.demo 包下日志等级为 off 即关闭
logging.level.com.example.demo = off

日志目录

在spring boot全局配置文件中,默认是在启动时输出日志文件,而不会针对性的单个生成一个日志文件,这就显得单个生成日志文件存储是多么重要,通常我们可以在spring boot全局配置文件中的application.properties文件中设置:

1
logging.file = spring.log

以上是针对于spring.log是存放于spring项目目录下的,如果运行后spinrg boot发现日志文件并没有写入则是可能spring boot未找到spring.log日志文件,而遇到这种事情 spring boot提供了一个完美的解决方案就是通过使用相对目录进行指定:

1
logging.file.name=/home/kunlun/Development/Web/Demo/Spring/demo/src/main/resources/spring.log

在spring boot logging日志中有一个非常好的功能呢个就是,如果当日志文件超过了10mb时,将会自动生成一个新的日志文件。与此同时spring boot提供了日志的自定义,如自定义错误的颜色以及时间、等级、长度、方法名、代码行等自定义,有兴趣的读者可通过查看spring boot 关于日志这一篇的文档:https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-logging

自动配置原理

在spring boot中,@SpringBootApplication注解之中,主要调用并使用了@EnableAutoConfiguration注解来进行标注,从而实现spring boot的自动配置功能,来帮助开发者解决spirng原生的蛋疼配置问题,我们可以通过阅读spring boot的@EnableAutoCOnfiguratiot的官方Java Doc的官方文档和其依赖的源代码查看并理解其Spirng boot自动配置的原理。

EnableAutoConfiguration.class

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 排除特定的自动配置类,使它们永远不会被应用。
* @return the classes to exclude
*/
Class<?>[] exclude() default {};

/**
* 排除特定的自动配置类名,这样它们就永远不会被应用
* @return the class names to exclude
* @since 1.3.0
*/
String[] excludeName() default {};

通常只要是一个标准的Spring boot代码都会使用 @SpringBootApplication注解,而通过阅读@SpringBootApplication对应的代码我们发现了@SpringBootApplication注解包含了@EnableAutoOnfiguration注解,而进一步阅读源代码后发现其在Springboot启动的时候会夹在主配置类AutoConfigurationImportSelector.class

AutoConfigurationImportSelector.class

1
2
3
4
5
6
7
8
9
10
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 判断@EnableAutoOnfiguration 注解是否开启,如没有开启则返回,默认开启(不然他也不会写!isEnable)
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// 自动配置条目
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
1
2
3
4
   // SpringFactoriesLoader从META-INF/spring中夹在实例化给指定的工厂,而这些可能存在于类中的路径中的多个jar文件中
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
}
1
2
3
4
5
6
7
8
9
10
11
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);

// 删除重复的配置 remoe Duplicates
configurations = removeDuplicates(configurations);

}

从上述代码我们可以大概的了解到Spring Boot是通过加载所有的jar文件来实现自动配置的,所以@SpringBootApplication是通过使用@EnableAutoConfiguration注解自动配置的原理即从class目录中搜索所有的spring.factories配置文件并将其中EnableAutoConfiguration对应的配置项通过java反射机制进行实例化之后夹在到spring ioc容器中。

Spring boot 核心注解

@SpringBootApplication 注解

在spring boot应用之中,咱把范围缩小一点,就是说spring boot应用的快速构建包下的启动类中,通常的类名称为 “XXXApplication.java”在spring boot的快速构建包下的启动类名称通常会为“DemoApplication”,而这个启动类名称前的(即Application前的)名称,是由你在https://start.spring.io/生成中的名称,比如本文是通过默认的名称来进行下载的,所以就是**DemoApplication**。
通常在未没有放上注解的情况下,这个就是一个程序入口类,并不是一个启动类,而在他真正成为一个启动类之前,需要在名为XXXApplication程序入口类中写上一个程序的启动类加上spring boot核心注解为@SpringBootApplication标注为应用的启动类,而不是单单的程序入口。你也可以理解为你一个处男,只有当有女朋友之后干了那个事情你才可以成为男人懂我意思吧?。

另外,在spring boot中的启动类都有一个非常标准的Java应用程序main方法,在main方法中通过SpringApplication.run(DemoApplication.class, args);,来启动spring boot应用,启动类的标准代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {

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

}

注意啊,上面代码不用你自己写,人家spring boot是遵循开箱即用原则的,虽然说最后还要导入后下载并更新maven,很多树上和文章上都会让读者手写上面的标准代码,其实他们也不一定是手写的,就是他妈的复制粘贴在这装比,凑个文章字数显得高达上,就跟本文一样。

在这其中,@SpringBootApplication是spring boot应用中的核心注解,也是一个组合注解,主要组合了@ComponentScan、@SpringBootConfiguration、@EnableAutoConfiguration等注解,我们可以通过查阅java的官方文档得知结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Open Declarationorg.springframework.boot.autoconfigure.SpringBootApplication

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters={@Filter(type=CUSTOM, classes={TypeExcludeFilter.class}), @Filter(type=CUSTOM, classes={AutoConfigurationExcludeFilter.class})})
@Target(value={TYPE})
@Retention(value=RUNTIME)
@Documented
@Inherited
Indicates a configuration class that declares one or more @Bean methods and also triggers auto-configuration and component scanning. This is a convenience annotation that is equivalent to declaring @Configuration, @EnableAutoConfiguration and @ComponentScan.

Since:
1.2.0
Author:
Phillip Webb
Stephane Nicoll
Andy Wilkinson

关闭特定的自动配置类

SpringBootApplication.class中我们可以通过其注释了解到@SpringBootApplication注解的作用和其他方式,如我们可以使用@SpringBootApplication注解来实现关闭特定的自动配置类

ID DA
@AliasFor(annotation = EnableAutoConfiguration.class) 排除特定的自动配置类
@AliasFor(annotation = EnableAutoConfiguration.class) 排除特定的自动配置类名

@EnableAutoConfiguration 注解

@EnableAutoConfiguration注解可以让spring boot可以根据当前的应用项目的依赖自动选择jar文件来自动配置

@ComponsentScan 注解

@ComonsentScan注解功能让Spring Boot自动扫描@SpringBootApplication所在类的同级包中的配置

Spring boot 构建与配置

在springboot中,主要分为快速构建和maven构建两种构建方式,其中两种方法各有特色,通常我们使用快速构建,只需将Maven java源代码导入开发环境中即可使用,而使用maven构建则需要进行各种文件的新建、编写、引入等,本文主要使用springboot快速构建。

springboot 快速构建包获取

获取springboot快速构建包可通过springboot官网https://start.spring.io/选择你的开发环境即可,之后解压,将快速构建springboot包导入开发环境中,生成其jar文件,当然也可直接运行主main进行执行。

导入或创建

将快速构建包导入开发环境

进入eclipse -> File -> Import -> Maven -> Existing Maven Projects 之后选择spring boot快速构建包即可导入至eclipse开发环境中,然后等待其maven的下载即可完成

使用 spring tool suite 创建

安装spring tool suite

在eclipse中,选择Help -> Eclipse Marketplace……然后搜索spring tool suite,然后选择你心仪的spring tool suite 版本点击安装即“install”即可。本文我们选择 spring tools 3 add-on for spring tools 4.3.9.15.release这个版本,当然读者也可以选择自己喜欢的进行安装,其操作方法都差不多,只需要能够创建spring boot项目即可。

使用spring tool suite 创建spring boot web项目

![](_v_images/20210102160008058_1918180747.png =891x)

eclipse开发环境下,选择 File -> New -> Other -> Spring Boot -> Spring Starter Project

之后选择你心仪的spring boot版本和你要创建的spring boot的类型,就是说你打算用spring boot干什么,和选择spring boot的类型,之后按照流程走就没什么问题了,最终可以创建spring boot项目就行,在此之后即创建了spring boot项目后就是等待的时间,就是说你还要等待很多,这个过程建议你睡一会,醒来之后就会发现下载完成了。

启动spring boot

编写控制器(Controller)

在spring boot项目中的src/main/java/中创建与DemoApplication.java同一目录级别的控制器即HelloWorldController.java

HelloWorldController.java

1
2
3
4
5
6
7
8
9
10
11
12
package com.example.demo;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloWorldController {
@GetMapping(value = "/test")
public String test() {
return "Hello,world!";
}
}

启动控制器(HelloWorldController.java)

选择自定义启动器即Run Configuration……,然后选择spring boot启动即可,访问 http://localhost:8080/test 即可查看spring 开发环境是否运行正常:

Spring boot全局配置

通常spring boot的全局配置都由src/main/resource/application.properties文件来进行配置,如配置端口、web上下文路径等。如需使用application.properties上配置端口和程序名称可通过下方code进行设置:

1
2
3
4
5
# 配置应用程序名称
spring.application.name = demoApplication

# 配置端口
server.port = 8081
📖 more posts 📖