开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

Java8之熟透Optional

发表于 2019-09-19

一、使用Optional引言

1.1、代码问题引出

在写程序的时候一般都遇到过 NullPointerException,所以经常会对程序进行非空的判断:

1
2
3
4
5
复制代码User user = getUserById(id);
if (user != null) {
String username = user.getUsername();
System.out.println("Username is: " + username); // 使用 username
}

为了解决这种尴尬的处境,JDK 终于在 Java8 的时候加入了 Optional 类,查看 Optional 的 javadoc 介绍:

1
复制代码A container object which may or may not contain a non-null value. If a value is present, isPresent() will return true and get() will return the value.

这是一个可以包含或者不包含非 null 值的容器。如果值存在则 isPresent()方法会返回 true,调用 get() 方法会返回该对象。

1.2、解决进阶

我们假设 getUserById 已经是个客观存在的不能改变的方法,那么利用 isPresent 和 get 两个方法,我们现在能写出下面的代码:

1
2
3
4
5
复制代码Optional<User> user = Optional.ofNullable(getUserById(id));
if (user.isPresent()) {
String username = user.get().getUsername();
System.out.println("Username is: " + username); // 使用 username
}

好像看着代码是优美了点,但是事实上这与之前判断 null 值的代码没有本质的区别,反而用 Optional 去封装 value,增加了代码量。所以我们来看看 Optional 还提供了哪些方法,让我们更好的(以正确的姿势)使用 Optional。

二、Optional三个静态构造方法

1)概述:

JDK 提供三个静态方法来构造一个 Optional:

  1. Optional.of(T value)
1
2
3
复制代码    public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}

该方法通过一个非 null 的 value 来构造一个 Optional,返回的 Optional 包含了 value 这个值。对于该方法,传入的参数一定不能为 null,否则便会抛出 NullPointerException。
2. Optional.ofNullable(T value)

1
2
3
复制代码    public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}

该方法和 of 方法的区别在于,传入的参数可以为 null —— 但是前面 javadoc 不是说 Optional 只能包含非 null 值吗?我们可以看看 ofNullable 方法的源码。

原来该方法会判断传入的参数是否为 null,如果为 null 的话,返回的就是 Optional.empty()。
3. Optional.empty()

1
2
3
4
5
复制代码    public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}

该方法用来构造一个空的 Optional,即该 Optional 中不包含值 —— 其实底层实现还是 如果 Optional 中的 value 为 null 则该 Optional 为不包含值的状态,然后在 API 层面将 Optional 表现的不能包含 null值,使得 Optional 只存在 包含值 和 不包含值 两种状态。

2)分析:

前面 javadoc 也有提到,Optional 的 isPresent() 方法用来判断是否包含值,get() 用来获取 Optional 包含的值 —— 值得注意的是,如果值不存在,即在一个Optional.empty 上调用 get() 方法的话,将会抛出 NoSuchElementException异常。

3)总结:

1)Optional.of(obj): 它要求传入的 obj 不能是 null 值的, 否则还没开始进入角色就倒在了 NullPointerException 异常上了.
2)Optional.ofNullable(obj): 它以一种智能的, 宽容的方式来构造一个 Optional 实例. 来者不拒, 传 null 进到就得到 Optional.empty(), 非 null 就调用 Optional.of(obj).
那是不是我们只要用 Optional.ofNullable(obj) 一劳永逸, 以不变应二变的方式来构造 Optional 实例就行了呢? 那也未必, 否则 Optional.of(obj) 何必如此暴露呢, 私有则可。

三、Optional常用方法详解

3.1、Optional常用方法概述

  1. Optional.of(T t)

将指定值用 Optional 封装之后返回,如果该值为 null,则抛出一个 NullPointerException 异常。
2. Optional.empty()

创建一个空的 Optional 实例。
3. Optional.ofNullable(T t)

将指定值用 Optional 封装之后返回,如果该值为 null,则返回一个空的 Optional 对象。
4. isPresent

如果值存在返回true,否则返回false
5. ifPresent

如果Optional实例有值则为其调用consumer ,否则不做处理。
要理解ifPresent方法,首先需要了解Consumer类。简答地说,Consumer类包含一个抽象方法。该抽象方法对传入的值进行处理,但没有返回值。 Java8支持不用接口直接通过lambda表达式传入参数。
如果Optional实例有值,调用ifPresent()可以接受接口段或lambda表达式。
6. Optional.get()

如果该值存在,将该值用 Optional 封装返回,否则抛出一个 NoSuchElementException 异常。
7. orElse(T t)

如果调用对象包含值,返回该值,否则返回t。
8. orElseGet(Supplier s)

如果调用对象包含值,返回该值,否则返回 s 获取的值。
9. orElseThrow()

它会在对象为空的时候抛出异常。
10. map(Function f)

如果值存在,就对该值执行提供的 mapping 函数调用。
11. flatMap(Function mapper)

如果值存在,就对该值执行提供的mapping 函数调用,返回一个 Optional 类型的值,否则就返回一个空的 Optional 对象。

3.2、Optional常用方法详解

3.2.1、ifPresent
1
2
3
4
复制代码    public void ifPresent(Consumer<? super T> consumer) {
if (value != null)
consumer.accept(value);
}

如果 Optional 中有值,则对该值调用 consumer.accept,否则什么也不做。
所以对于引言上的例子,我们可以修改为:

1
2
复制代码Optional<User> user = Optional.ofNullable(getUserById(id));
user.ifPresent(u -> System.out.println("Username is: " + u.getUsername()));
3.2.2、orElse
1
2
3
复制代码    public T orElse(T other) {
return value != null ? value : other;
}

如果 Optional 中有值则将其返回,否则返回 orElse 方法传入的参数。

1
2
3
4
5
复制代码User user = Optional
.ofNullable(getUserById(id))
.orElse(new User(0, "Unknown"));

System.out.println("Username is: " + user.getUsername());
3.2.3、orElseGet
1
2
3
复制代码    public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
}

orElseGet 与 orElse 方法的区别在于,orElseGet 方法传入的参数为一个 Supplier接口的实现 —— 当 Optional 中有值的时候,返回值;当 Optional 中没有值的时候,返回从该 Supplier 获得的值。

1
2
3
4
5
复制代码User user = Optional
.ofNullable(getUserById(id))
.orElseGet(() -> new User(0, "Unknown"));

System.out.println("Username is: " + user.getUsername());
3.2.4、orElseThrow
1
2
3
4
5
6
7
复制代码public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}

orElseThrow 与 orElse 方法的区别在于,orElseThrow 方法当 Optional 中有值的时候,返回值;没有值的时候会抛出异常,抛出的异常由传入的 exceptionSupplier 提供。

举例说明:

​ 在 SpringMVC 的控制器中,我们可以配置统一处理各种异常。查询某个实体时,如果数据库中有对应的记录便返回该记录,否则就可以抛出 EntityNotFoundException ,处理 EntityNotFoundException 的方法中我们就给客户端返回Http 状态码 404 和异常对应的信息 —— orElseThrow 完美的适用于这种场景。

1
2
3
4
5
6
7
8
9
10
复制代码@RequestMapping("/{id}")
public User getUser(@PathVariable Integer id) {
Optional<User> user = userService.getUserById(id);
return user.orElseThrow(() -> new EntityNotFoundException("id 为 " + id + " 的用户不存在"));
}

@ExceptionHandler(EntityNotFoundException.class)
public ResponseEntity<String> handleException(EntityNotFoundException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
}
3.2.5、map
1
2
3
4
5
6
7
8
复制代码    public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Optional.ofNullable(mapper.apply(value));
}
}

如果当前 Optional 为 Optional.empty,则依旧返回 Optional.empty;否则返回一个新的 Optional,该 Optional 包含的是:函数 mapper 在以 value 作为输入时的输出值。

1
2
3
4
复制代码String username = Optional.ofNullable(getUserById(id))
.map(user -> user.getUsername())
.orElse("Unknown")
.ifPresent(name -> System.out.println("Username is: " + name));

而且我们可以多次使用 map 操作:

1
2
3
4
5
6
复制代码Optional<String> username = Optional.ofNullable(getUserById(id))
.map(user -> user.getUsername())
.map(name -> name.toLowerCase())
.map(name -> name.replace('_', ' '))
.orElse("Unknown")
.ifPresent(name -> System.out.println("Username is: " + name));
3.2.6、flatMap
1
2
3
4
5
6
7
8
复制代码    public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Objects.requireNonNull(mapper.apply(value));
}
}

flatMap 方法与 map 方法的区别在于,map 方法参数中的函数 mapper 输出的是值,然后 map 方法会使用 Optional.ofNullable 将其包装为 Optional;而 flatMap 要求参数中的函数 mapper 输出的就是 Optional。

1
2
3
4
5
复制代码Optional<String> username = Optional.ofNullable(getUserById(id))
.flatMap(user -> Optional.of(user.getUsername()))
.flatMap(name -> Optional.of(name.toLowerCase()))
.orElse("Unknown")
.ifPresent(name -> System.out.println("Username is: " + name));
3.2.7、filter
1
2
3
4
5
6
7
复制代码    public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent())
return this;
else
return predicate.test(value) ? this : empty();
}

filter 方法接受一个 Predicate 来对 Optional 中包含的值进行过滤,如果包含的值满足条件,那么还是返回这个 Optional;否则返回 Optional.empty。

1
2
3
4
5
复制代码Optional<String> username = Optional.ofNullable(getUserById(id))
.filter(user -> user.getId() < 10)
.map(user -> user.getUsername());
.orElse("Unknown")
.ifPresent(name -> System.out.println("Username is: " + name));

四、Optional使用示例

4.1、使用展示一

当 user.isPresent() 为真, 获得它关联的 orders的映射集合, 为假则返回一个空集合时, 我们用上面的 orElse, orElseGet 方法都乏力时, 那原本就是 map 函数的责任, 我们可以这样一行:

1
2
3
4
5
6
7
8
复制代码return user.map(u -> u.getOrders()).orElse(Collections.emptyList())

//上面避免了我们类似 Java 8 之前的做法
if(user.isPresent()) {
return user.get().getOrders();
} else {
return Collections.emptyList();
}

map 是可能无限级联的, 比如再深一层, 获得用户名的大写形式:

1
2
3
复制代码return user.map(u -> u.getUsername())
.map(name -> name.toUpperCase())
.orElse(null);

以前的做法:

1
2
3
4
5
6
7
8
9
10
11
复制代码User user = .....
if(user != null) {
String name = user.getUsername();
if(name != null) {
return name.toUpperCase();
} else {
return null;
}
} else {
return null;
}

filter() :如果有值并且满足条件返回包含该值的Optional,否则返回空Optional。

1
2
复制代码Optional<String> longName = name.filter((value) -> value.length() > 6);  
System.out.println(longName.orElse("The name is less than 6 characters"));

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

分布式事务(TX-Lcn)简单使用

发表于 2019-09-19

原理:

1
2
3
4
复制代码    创建一个事务管理组Tm项目。
LCN把事务注册到Tm中。然后结束后一起提交事务。
TCC先把事务提交。然后错误后进入cl方法中对数据进行修改。
只是简单的使用整理。要深入了解请看官方文档。

文档:

1
2
复制代码    源码地址:https://github.com/codingapi/tx-lcn
中文文档:http://www.txlcn.org/zh-cn/docs/preface.html

步骤:

1
2
3
4
5
6
7
复制代码    1.创建tx-manage数据库和表
2.创建Tm项目。修改配置。
3.启动tm项目,并查看是否成功

4.使用tc并注册到Tm
5.使用@LcnTransaction模式
6.使用@TccTransaction模式

1.创建tx-manage数据库和表

1
2
3
4
5
6
7
8
9
10
11
12
13
复制代码创建数据库和表名
CREATE TABLE `t_tx_exception` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`group_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`unit_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`mod_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`transaction_state` tinyint(4) NULL DEFAULT NULL,
`registrar` tinyint(4) NULL DEFAULT NULL,
`remark` varchar(4096) NULL DEFAULT NULL,
`ex_state` tinyint(4) NULL DEFAULT NULL COMMENT '0 未解决 1已解决',
`create_time` datetime(0) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

2.创建Tm项目

1.添加依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
复制代码<!--mysql连接-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.45</version>
</dependency>

<!--分布式事务管理tm-->
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-tm</artifactId>
<version>${codingapi.txlcn.version}</version>
</dependency>
2.启动类添加@EnableTransactionManagerServer注解

3.修改Tm配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
复制代码server:
port: 7970
spring:
application:
name: tx-manager
datasource:
# mysql数据源
driver-class-name: com.mysql.jdbc.Driver
password: 123456
url: jdbc:mysql://127.0.0.1:3306/tx-manager?characterEncoding=UTF-8
username: root
redis:
# redis数据源 改成自己的。
database: 28
host:
password:
port:
tx-lcn:
manager:
# 登录密码
admin-key: 123456

# 详情可看 http://www.txlcn.org/zh-cn/docs/setting/manager.html
4.Tm项目整体

3.启动项目并查看

1.输入http://localhost:7970/admin/index.html#/login

2.根据配置文件里面admin-key的值。登录进入

4.使用tc并注册到Tm

1.添加依赖(微服务相关的和Mybatis相关的就不说了)
1
2
3
4
5
6
7
8
9
10
11
12
复制代码<!--tc-->
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-tc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--tc与tm通讯-->
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-txmsg-netty</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
2.启动类添加@EnableDistributedTransaction注解
3.配置文件编写Tm项目地址。
1
2
3
4
5
复制代码#Tm项目地址。 默认是127.0.0.1:8070,如果再服务器上的话要改成对应的地址。 
#8070是Tm默认的监听端口,需要更改的话去看TM的配置文件信息,TM监听Socket端口. tx-lcn.manager.port=8070。默认是Tm的启动端口+100。虽然文档写的是-100.实际上是+100
tx-lcn:
client:
manager-address: 127.0.0.1:8070
4.启动项目。 查看是否注册到Tm中去。

5.重复1-3步,启动另一个服务。

5.使用@LcnTransaction模式

1.在消费者和提供者方法上都添加上@LcnTransaction注解。
1
2
3
复制代码流程: A: 插入数据a  ->  B: 插入数据b  ->  A: 是否抛出异常
抛出异常:事务不提交。无数据
不抛出异常:事务提交。有两条数据

2.然后测试。不带上ex参数。项目跑成功。并插入了两条数据

3.带上ex参数。项目抛出异常。然后查看数据是否提交(回滚)。发现数据没有改变。说明分布式事务Lcn模式使用成功。

6.使用@LcnTransaction模式

1.流程、逻辑不变。把b项目中的@LcnTransaction换成@TccTransaction注解

2.可以在B项目插入数据后打断点。然后查看数据库。数据是否插入。

3.执行完程序。查看是否进入cf与cl方法。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

重学Java(一):与《Java编程思想》的不解之缘

发表于 2019-09-19

说起来非常惭愧,我在 2008 年的时候就接触了 Java,但一直到现在(2018 年 10 月 10 日),基础知识依然非常薄弱。用一句话自嘲就是:十年 IT 老兵,Java 菜鸡一枚。

于是,我想,不如静下心来,重新读一遍那些经典的 Java 技术书,并且没读完一章就输出一篇原创技术文章。从哪一本开始呢?想了一想,还是从《Java 编程思想》开始吧!毕竟这本书赢得了全球程序员的广泛赞誉,从 Java 的基础语法到最高级特性,都能指导我们 Java 程序员轻松掌握。

记得刚上大学那会,就买了一本影印版的《Java 编程思想》,但由于初学 Java,对编程极度缺乏信心,导致看这本书有一种看天书的感觉。后来,去苏州参加工作的时候把它作为最宝贵的纪念品带了过去。

2014 年回洛阳的时候它送给了一位关系不错的同事,权当是分别的礼物吧。2016 年的时候,我又重新买了一本,希望自己能够夯实一下基础。但事与愿违,它被我束之高阁了——又两年过去了,我重新捧起它,总觉得有一种负罪感。

读一本书,最好能从它的前言开始。那么我们就来看看 Bruce Eckel 在前言里都说了些什么吧。

01、Java 的核心目的是“为程序员减少复杂性”。

James Gosling 创建 Java 语言的初衷是:“减少开发健壮代码所需的时间和困难”。尽管这个目标导致 Java 的运行效率偏慢,但与用 C++ 开发相同的程序相比,Java 只需要一半甚至更少的时间。

作为程序员,这是我们希望看到的。少敲代码省下来的那一部分时间,可以约个妹子去看场电影,放松一下,对吧?况且,Java 一直在更新,性能也不断地被优化。

记得上大学那会,我们专业只有两个班,一个班学 Java,一个班级学 C++。结果大学毕业后,C++ 的同学几乎都转了行,有些同学反馈说因为 C++ 的指针太飘忽不定了,难学难懂难掌握(C++ 表示不服,怎么能这样莫名其妙地泼脏水呢)。

02、并发编程确实很难。

Bruce Eckel 吐露心声说自己也曾深陷“并发”泥潭,但经过“数月的努力,还是走了出来”。所以,各位,千万不要丧失驾驭并发编程的信心啊,尽管并发编程是真的难。

并发是什么呢?通常情况下,并发是指“系统能够同时并行处理很多请求”。我们来看一下并发常用的一些指标。

1)响应时间(Response Time):系统从接收请求到做出回应所花费的时间。

2)吞吐量(Throughput):单位时间内处理的请求数量。最明显的例子就是高速通道上的 ETC 和普通车道,显然 ETC 的吞吐量更大,因为不需要在进站的时候从窗口取卡,在出站的时候还卡缴费。

3)并发用户数:同时承载正常使用系统功能的用户数量。

如何提升系统的并发能力呢?

1)提升单机硬件配置。比如说增加 CPU 核数(从 2 个到 4 个,从 4 个到 8 个),升级网卡到万兆,升级硬盘为 SSD(固态硬盘,比普通硬盘读写更快、质量更轻、能耗更低、体积更小),扩充系统内存(从 64G 到 128G)。

2)改善单机架构配置。比如使用内存读写而不是每次都读写数据库。

3)增加服务器数量。单机性能总是有极限的,但服务器集群数量可以很庞大。

好了,本篇文章到此就要结束了。我从《Java 编程思想》的前言里读到了以上这些内容,你呢?

PS:这篇文章写于 2018 年 10 月 10 日,现在读起来感觉当时写得太烂了,但很适合作为《重学Java》系列文章的第一篇(毕竟开局嘛)。

PPS:关注「沉默王二」公众号后,回复关键字「Java」即可免费获取价值 1999 元的 Java 学习资料。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

程序员们的三高:高并发、高性能、高可用 一、高并发 二、高性

发表于 2019-09-18

一、高并发

简介

高并发(High Concurrency)是互联网分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计保证系统能够同时并行处理很多请求。
高并发相关常用的一些指标有响应时间(Response Time),吞吐量(Throughput),每秒查询率QPS(Query Per Second),并发用户数等。

  • 响应时间:系统对请求做出响应的时间。例如系统处理一个HTTP请求需要200ms,这个200ms就是系统的响应时间。
  • 吞吐量:单位时间内处理的请求数量。
  • QPS:每秒响应请求数。在互联网领域,这个指标和吞吐量区分的没有这么明显。
  • 并发用户数:同时承载正常使用系统功能的用户数量。例如一个即时通讯系统,同时在线量一定程度上代表了系统的并发用户数。

如何提高并发能力

  • 垂直扩展(Scale Up)
    1. 增强单机硬件性能(优先):例如:增加CPU核数如32核,升级更好的网卡如万兆,升级更好的硬盘如SSD,扩充硬盘容量如2T,扩充系统内存如128G。
    2. 提升单机架构性能:例如:使用Cache来减少IO次数,使用异步来增加单服务吞吐量,使用无锁数据结构来减少响应时间。
    3. 总结:管是提升单机硬件性能,还是提升单机架构性能,都有一个致命的不足:单机性能总是有极限的。所以互联网分布式架构设计高并发终极解决方案还是水平扩展。
  • 水平扩展(Scale Out)
    1. 只要增加服务器数量,就能线性扩充系统性能。水平扩展对系统架构设计是有要求的,难点在于:如何在架构各层进行可水平扩展的设计。

二、高性能

简介

  1. 简单的说,高性能(High Performance)就是指程序处理速度快,所占内存少,cpu占用率低。
  2. 高并发和高性能是紧密相关的,提高应用的性能,是肯定可以提高系统的并发能力的。
  3. 应用性能优化的时候,对于计算密集型和IO密集型还是有很大差别,需要分开来考虑。
  4. 增加服务器资源(CPU、内存、服务器数量),绝大部分时候是可以提高应用的并发能力和性能
    (前提是应用能够支持多任务并行计算,多服务器分布式计算才行),但也是要避免其中的一些问题,才可以更好的更有效率的利用服务器资源。

提高性能的注意事项

  1. 避免因为IO阻塞让CPU闲置,导致CPU的浪费。
  2. 避免多线程间增加锁来保证同步,导致并行系统串行化。
  3. 免创建、销毁、维护太多进程、线程,导致操作系统浪费资源在调度上。
  4. 避免分布式系统中多服务器的关联,比如:依赖同一个mysql,程序逻辑中使用分布式锁,导致瓶颈在mysql,分布式又变成串行化运算。

三、高可用

简介

高可用性(High Availability)通常来描述一个系统经过专门的设计,从而减少停工时间,而保持其服务的高度可用性(一直都能用)。

  • 全年停机不能超过31.5秒,
  • 6个9的性能:一直能用的概率为99.9999%

高可用注意事项

  1. 避免单点:使用单个服务器,一旦该服务器意外宕机,将导致服务不可用
  2. 使用“集群”:一台服务器挂了,还有其他后备服务器能够顶上
  3. 心跳机制:用于监控服务器状态,挂了就进行故障修复

四、 举例

Redis的主从复制

1. 应用场景

电子商务网站上的商品,一般都是一次上传,无数次浏览的,说专业点也就是”多读少写”。

2. 实现原理

一个Redis服务可以有多个该服务的复制品,这个Redis服务称为Master,其它复制称为Slaves。

如图中所示,我们将一台Redis服务器作主库(Matser),其他三台作为从库(Slave),主库只负责写数据,每次有数据更新都将更新的数据同步到它所有的从库,而从库只负责读数据。这样一来,就有了两个好处:

  1. 读写分离:不仅可以提高服务器的负载能力,并且可以根据读请求的规模自由增加或者减少从库的数量。
  2. 数据被复制成了了好几份,就算有一台机器出现故障,也可以使用其他机器的数据快速恢复。

注意事项:在Redis主从模式中,一台主库可以拥有多个从库,但是一个从库只能隶属于一个主库。


PS:

  1. 文章来自各种资源的整理,如有侵权请告知删除。
  2. 转载本文请注明出处

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

面试官问你B树和B+树,就把这篇文章丢给他

发表于 2019-09-18

原文链接:面试官问你B树和B+树,就把这篇文章丢给他

1 B树

在介绍B+树之前, 先简单的介绍一下B树,这两种数据结构既有相似之处,也有他们的区别,最后,我们也会对比一下这两种数据结构的区别。

1.1 B树概念

B树也称B-树,它是一颗多路平衡查找树。二叉树我想大家都不陌生,其实,B树和后面讲到的B+树也是从最简单的二叉树变换而来的,并没有什么神秘的地方,下面我们来看看B树的定义。

  • 每个节点最多有m-1个关键字(可以存有的键值对)。
  • 根节点最少可以只有1个关键字。
  • 非根节点至少有m/2个关键字。
  • 每个节点中的关键字都按照从小到大的顺序排列,每个关键字的左子树中的所有关键字都小于它,而右子树中的所有关键字都大于它。
  • 所有叶子节点都位于同一层,或者说根节点到每个叶子节点的长度都相同。
  • 每个节点都存有索引和数据,也就是对应的key和value。

所以,根节点的关键字数量范围:1 <= k <= m-1,非根节点的关键字数量范围:m/2 <= k <= m-1。

另外,我们需要注意一个概念,描述一颗B树时需要指定它的阶数,阶数表示了一个节点最多有多少个孩子节点,一般用字母m表示阶数。

我们再举个例子来说明一下上面的概念,比如这里有一个5阶的B树,根节点数量范围:1 <= k <= 4,非根节点数量范围:2 <= k <= 4。

下面,我们通过一个插入的例子,讲解一下B树的插入过程,接着,再讲解一下删除关键字的过程。

1.2 B树插入

插入的时候,我们需要记住一个规则:判断当前结点key的个数是否小于等于m-1,如果满足,直接插入即可,如果不满足,将节点的中间的key将这个节点分为左右两部分,中间的节点放到父节点中即可。

例子:在5阶B树中,结点最多有4个key,最少有2个key(注意:下面的节点统一用一个节点表示key和value)。

  • 插入18,70,50,40

  • 插入22

插入22时,发现这个节点的关键字已经大于4了,所以需要进行分裂,分裂的规则在上面已经讲了,分裂之后,如下。

  • 接着插入23,25,39

分裂,得到下面的。

更过的插入的过程就不多介绍了,相信有这个例子你已经知道怎么进行插入操作了。

1.3 B树的删除操作

B树的删除操作相对于插入操作是相对复杂一些的,但是,你知道记住几种情况,一样可以很轻松的掌握的。

  • 现在有一个初始状态是下面这样的B树,然后进行删除操作。

  • 删除15,这种情况是删除叶子节点的元素,如果删除之后,节点数还是大于m/2,这种情况只要直接删除即可。

  • 接着,我们把22删除,这种情况的规则:22是非叶子节点,对于非叶子节点的删除,我们需要用后继key(元素)覆盖要删除的key,然后在后继key所在的子支中删除该后继key。对于删除22,需要将后继元素24移到被删除的22所在的节点。

此时发现26所在的节点只有一个元素,小于2个(m/2),这个节点不符合要求,这时候的规则(向兄弟节点借元素):如果删除叶子节点,如果删除元素后元素个数少于(m/2),并且它的兄弟节点的元素大于(m/2),也就是说兄弟节点的元素比最少值m/2还多,将先将父节点的元素移到该节点,然后将兄弟节点的元素再移动到父节点。这样就满足要求了。

我们看看操作过程就更加明白了。

  • 接着删除28,删除叶子节点,删除后不满足要求,所以,我们需要考虑向兄弟节点借元素,但是,兄弟节点也没有多的节点(2个),借不了,怎么办呢?如果遇到这种情况,首先,还是将先将父节点的元素移到该节点,然后,将当前节点及它的兄弟节点中的key合并,形成一个新的节点。

移动之后,跟兄弟节点合并。

删除就只有上面的几种情况,根据不同的情况进行删除即可。

上面的这些介绍,相信对于B树已经有一定的了解了,接下来的一部分,我们接着讲解B+树,我相信加上B+树的对比,就更加清晰明了了。

2 B+树

2.1 B+树概述

B+树其实和B树是非常相似的,我们首先看看相同点。

  • 根节点至少一个元素
  • 非根节点元素范围:m/2 <= k <= m-1

不同点。

  • B+树有两种类型的节点:内部结点(也称索引结点)和叶子结点。内部节点就是非叶子节点,内部节点不存储数据,只存储索引,数据都存储在叶子节点。
  • 内部结点中的key都按照从小到大的顺序排列,对于内部结点中的一个key,左树中的所有key都小于它,右子树中的key都大于等于它。叶子结点中的记录也按照key的大小排列。
  • 每个叶子结点都存有相邻叶子结点的指针,叶子结点本身依关键字的大小自小而大顺序链接。
  • 父节点存有右孩子的第一个元素的索引。

下面我们看一个B+树的例子,感受感受它吧!

2.2 插入操作

对于插入操作很简单,只需要记住一个技巧即可:当节点元素数量大于m-1的时候,按中间元素分裂成左右两部分,中间元素分裂到父节点当做索引存储,但是,本身中间元素还是分裂右边这一部分的。

下面以一颗5阶B+树的插入过程为例,5阶B+树的节点最少2个元素,最多4个元素。

  • 插入5,10,15,20

  • 插入25,此时元素数量大于4个了,分裂

  • 接着插入26,30,继续分裂

有了这几个例子,相信插入操作没什么问题了,下面接着看看删除操作。

2.3 删除操作

对于删除操作是比B树简单一些的,因为叶子节点有指针的存在,向兄弟节点借元素时,不需要通过父节点了,而是可以直接通过兄弟节移动即可(前提是兄弟节点的元素大于m/2),然后更新父节点的索引;如果兄弟节点的元素不大于m/2(兄弟节点也没有多余的元素),则将当前节点和兄弟节点合并,并且删除父节点中的key,下面我们看看具体的实例。

  • 初始状态

  • 删除10,删除后,不满足要求,发现左边兄弟节点有多余的元素,所以去借元素,最后,修改父节点索引

  • 删除元素5,发现不满足要求,并且发现左右兄弟节点都没有多余的元素,所以,可以选择和兄弟节点合并,最后修改父节点索引

  • 发现父节点索引也不满足条件,所以,需要做跟上面一步一样的操作

这样,B+树的删除操作也就完成了,是不是看完之后,觉得非常简单!

3 B树和B+树总结

B+树相对于B树有一些自己的优势,可以归结为下面几点。

  • 单一节点存储的元素更多,使得查询的IO次数更少,所以也就使得它更适合做为数据库MySQL的底层数据结构了。
  • 所有的查询都要查找到叶子节点,查询性能是稳定的,而B树,每个节点都可以查找到数据,所以不稳定。
  • 所有的叶子节点形成了一个有序链表,更加便于查找。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

nginx 这一篇就够了 nginx 安装 nginx 服务

发表于 2019-09-18

nginx

  • 安装
    • 安装依赖
    • 下载
    • 编译安装
      • 编译时将 ssl 模块静态编译
  • nginx 服务架构
    • 模块化结构
      • 模块化开发
      • nginx 的模块化结构
    • nginx 的模块清单
    • nginx 的 web 请求处理机制
  • nginx 配置文件实例
  • nginx 服务器基础配置指令
    • nginx.conf 文件的结构
    • nginx 运行相关的 Global 部分
      • 配置运行 nginx 服务器用户
      • 配置允许生成的 worker process 数
      • 配置 nginx 进程 PID 存放路径
      • 配置错误日志的存放路径
      • 配置文件的引入
    • 与用户的网络连接相关的 events
      • 设置网络连接的序列化
      • 设置是否允许同时接收多个网络连接
      • 事件驱动模型的选择
      • 配置最大连接数
    • http
      • http Global 代理 - 缓存 - 日志 - 第三方模块配置
        • 定义 MIME-Type
        • 自定义服务日志
        • 配置允许 sendfile 方式传输文件
        • 配置连接超时时间
        • 单连接请求数上限
      • server
        • 配置网络监听
        • 基于名称的虚拟主机配置
        • 配置 https 证书
        • 基于 IP 的虚拟主机配置
        • 配置 location 块
        • [root] 配置请求的根目录
        • [alias] 更改 location 的 URI
        • 设置网站的默认首页
        • 设置网站的错误页面
        • 基于 IP 配置 nginx 的访问权限
        • 基于密码配置 nginx 的访问权限
  • 应用
    • 架设简单文件服务器
    • nginx 正向代理
    • nginx 服务器基础配置实例
      • 测试 myServer1 的访问
      • 测试 myServer2 的访问
    • 使用缓存
    • 使用 location 反向代理到已有网站
    • 其他
      • ngx_http_sub_module 替换响应中内容
      • 配置 http 强制跳转 https

安装

安装依赖

安装 nginx 之前,确保系统已经安装 gcc、openssl-devel、pcre-devel 和 zlib-devel 软件库

  • gcc 可以通过光盘直接选择安装
  • openssl-devel、zlib-devel 可以通过光盘直接选择安装,https 时使用
  • pcre-devel 安装 pcre 库是为了使 nginx 支持 HTTP Rewrite 模块

下载

nginx 下载

编译安装

通过上面的下载页下载最新的稳定版

1
2
3
4
5
复制代码#wget http://nginx.org/download/nginx-1.8.0.tar.gz
#tar xzvf nginx-1.8.0.tar.gz
#cd nginx-1.8.0
#./configure --prefix=/opt/X_nginx/nginx --with-http_ssl_module
#make && sudo make install
  • –prefix=/opt/X_nginx/nginx 安装目录
  • –with-http_ssl_module 添加 https 支持

编译时将 ssl 模块静态编译

1
2
3
4
5
复制代码./configure  --prefix=/opt/X_nginx/nginx \
--with-openssl=../openssl-1.0.2l \
--with-zlib=../zlib-1.2.11 \
--with-pcre=../pcre-8.41 \
--with-http_ssl_module

nginx 服务架构

模块化结构

nginx 服务器的开发完全遵循模块化设计思想

模块化开发

  1. 单一职责原则,一个模块只负责一个功能
  2. 将程序分解,自顶向下,逐步求精
  3. 高内聚,低耦合

nginx 的模块化结构

  • 核心模块:nginx 最基本最核心的服务,如进程管理、权限控制、日志记录;
  • 标准 HTTP 模块:nginx 服务器的标准 HTTP 功能;
  • 可选 HTTP 模块:处理特殊的 HTTP 请求
  • 邮件服务模块:邮件服务
  • 第三方模块:作为扩展,完成特殊功能

nginx 的模块清单

  • 核心模块
+ ngx\_core
+ ngx\_errlog
+ ngx\_conf
+ ngx\_events
+ ngx\_event\_core
+ ngx\_epll
+ ngx\_regex
  • 标准 HTTP 模块
+ ngx\_http
+ ngx\_http\_core #配置端口,URI 分析,服务器相应错误处理,别名控制 (alias) 等
+ ngx\_http\_log #自定义 access 日志
+ ngx\_http\_upstream #定义一组服务器,可以接受来自 proxy, Fastcgi,Memcache 的重定向;主要用作负载均衡
+ ngx\_http\_static
+ ngx\_http\_autoindex #自动生成目录列表
+ ngx\_http\_index #处理以`/`结尾的请求,如果没有找到 index 页,则看是否开启了`random_index`;如开启,则用之,否则用 autoindex
+ ngx\_http\_auth\_basic #基于 http 的身份认证 (auth\_basic)
+ ngx\_http\_access #基于 IP 地址的访问控制 (deny,allow)
+ ngx\_http\_limit\_conn #限制来自客户端的连接的响应和处理速率
+ ngx\_http\_limit\_req #限制来自客户端的请求的响应和处理速率
+ ngx\_http\_geo
+ ngx\_http\_map #创建任意的键值对变量
+ ngx\_http\_split\_clients
+ ngx\_http\_referer #过滤 HTTP 头中 Referer 为空的对象
+ ngx\_http\_rewrite #通过正则表达式重定向请求
+ ngx\_http\_proxy
+ ngx\_http\_fastcgi #支持 fastcgi
+ ngx\_http\_uwsgi
+ ngx\_http\_scgi
+ ngx\_http\_memcached
+ ngx\_http\_empty\_gif #从内存创建一个 1×1 的透明 gif 图片,可以快速调用
+ ngx\_http\_browser #解析 http 请求头部的 User-Agent 值
+ ngx\_http\_charset #指定网页编码
+ ngx\_http\_upstream\_ip\_hash
+ ngx\_http\_upstream\_least\_conn
+ ngx\_http\_upstream\_keepalive
+ ngx\_http\_write\_filter
+ ngx\_http\_header\_filter
+ ngx\_http\_chunked\_filter
+ ngx\_http\_range\_header
+ ngx\_http\_gzip\_filter
+ ngx\_http\_postpone\_filter
+ ngx\_http\_ssi\_filter
+ ngx\_http\_charset\_filter
+ ngx\_http\_userid\_filter
+ ngx\_http\_headers\_filter #设置 http 响应头
+ ngx\_http\_copy\_filter
+ ngx\_http\_range\_body\_filter
+ ngx\_http\_not\_modified\_filter
  • 可选 HTTP 模块
+ ngx\_http\_addition #在响应请求的页面开始或者结尾添加文本信息
+ ngx\_http\_degradation #在低内存的情况下允许服务器返回 444 或者 204 错误
+ ngx\_http\_perl
+ ngx\_http\_flv #支持将 Flash 多媒体信息按照流文件传输,可以根据客户端指定的开始位置返回 Flash
+ ngx\_http\_geoip #支持解析基于 GeoIP 数据库的客户端请求
+ ngx\_google\_perftools
+ ngx\_http\_gzip #gzip 压缩请求的响应
+ ngx\_http\_gzip\_static #搜索并使用预压缩的以.gz 为后缀的文件代替一般文件响应客户端请求
+ ngx\_http\_image\_filter #支持改变 png,jpeg,gif 图片的尺寸和旋转方向
+ ngx\_http\_mp4 #支持.mp4,.m4v,.m4a 等多媒体信息按照流文件传输,常与 ngx\_http\_flv 一起使用
+ ngx\_http\_random\_index #当收到 / 结尾的请求时,在指定目录下随机选择一个文件作为 index
+ ngx\_http\_secure\_link #支持对请求链接的有效性检查
+ ngx\_http\_ssl #支持 https
+ ngx\_http\_stub\_status
+ ngx\_http\_sub\_module #使用指定的字符串替换响应中的信息
+ ngx\_http\_dav #支持 HTTP 和 WebDAV 协议中的 PUT/DELETE/MKCOL/COPY/MOVE 方法
+ ngx\_http\_xslt #将 XML 响应信息使用 XSLT 进行转换
  • 邮件服务模块
+ ngx\_mail\_core
+ ngx\_mail\_pop3
+ ngx\_mail\_imap
+ ngx\_mail\_smtp
+ ngx\_mail\_auth\_http
+ ngx\_mail\_proxy
+ ngx\_mail\_ssl
  • 第三方模块
+ echo-nginx-module #支持在 nginx 配置文件中使用 echo/sleep/time/exec 等类 Shell 命令
+ memc-nginx-module
+ rds-json-nginx-module #使 nginx 支持 json 数据的处理
+ lua-nginx-module

nginx 的 web 请求处理机制

作为服务器软件,必须具备并行处理多个客户端的请求的能力, 工作方式主要以下 3 种:

  • 多进程 (Apache)
    • 优点:设计和实现简单;子进程独立
    • 缺点:生成一个子进程要内存复制,在资源和时间上造成额外开销
  • 多线程 (IIS)
    • 优点:开销小
    • 缺点:开发者自己要对内存进行管理;线程之间会相互影响
  • 异步方式 (nginx)

经常说道异步非阻塞这个概念, 包含两层含义:

通信模式:

  • 同步:发送方发送完请求后,等待并接受对方的回应后,再发送下个请求
  • 异步:发送方发送完请求后,不必等待,直接发送下个请求

nginx 配置文件实例

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
复制代码#定义 nginx 运行的用户和用户组
user www www;

#nginx 进程数,建议设置为等于 CPU 总核心数。
worker_processes 8;

#nginx 默认没有开启利用多核 CPU, 通过增加 worker_cpu_affinity 配置参数来充分利用多核 CPU 以下是 8 核的配置参数
worker_cpu_affinity 00000001 00000010 00000100 00001000 00010000 00100000 01000000 10000000;

#全局错误日志定义类型,[ debug | info | notice | warn | error | crit ]
error_log /var/log/nginx/error.log info;

#进程文件
pid /var/run/nginx.pid;

#一个 nginx 进程打开的最多文件描述符数目,理论值应该是最多打开文件数(系统的值 ulimit -n)与 nginx 进程数相除,但是 nginx 分配请求并不均匀,所以建议与 ulimit -n 的值保持一致。
worker_rlimit_nofile 65535;

#工作模式与连接数上限
events
{
#参考事件模型,use [ kqueue | rtsig | epoll | /dev/poll | select | poll ]; epoll 模型是 Linux 2.6 以上版本内核中的高性能网络 I/O 模型,如果跑在 FreeBSD 上面,就用 kqueue 模型。
#epoll 是多路复用 IO(I/O Multiplexing) 中的一种方式,但是仅用于 linux2.6 以上内核,可以大大提高 nginx 的性能
use epoll;

############################################################################
#单个后台 worker process 进程的最大并发链接数
#事件模块指令,定义 nginx 每个进程最大连接数,默认 1024。最大客户连接数由 worker_processes 和 worker_connections 决定
#即 max_client=worker_processes*worker_connections, 在作为反向代理时:max_client=worker_processes*worker_connections / 4
worker_connections 65535;
############################################################################
}

#设定 http 服务器
http {
include mime.types; #文件扩展名与文件类型映射表
default_type application/octet-stream; #默认文件类型
#charset utf-8; #默认编码

server_names_hash_bucket_size 128; #服务器名字的 hash 表大小
client_header_buffer_size 32k; #上传文件大小限制
large_client_header_buffers 4 64k; #设定请求缓
client_max_body_size 8m; #设定请求缓
sendfile on; #开启高效文件传输模式,sendfile 指令指定 nginx 是否调用 sendfile 函数来输出文件,对于普通应用设为 on,如果用来进行下载等应用磁盘 IO 重负载应用,可设置为 off,以平衡磁盘与网络 I/O 处理速度,降低系统的负载。注意:如果图片显示不正常把这个改成 off。
autoindex on; #开启目录列表访问,合适下载服务器,默认关闭。
tcp_nopush on; #防止网络阻塞
tcp_nodelay on; #防止网络阻塞

##连接客户端超时时间各种参数设置##
keepalive_timeout 120; #单位是秒,客户端连接时时间,超时之后服务器端自动关闭该连接 如果 nginx 守护进程在这个等待的时间里,一直没有收到浏览发过来 http 请求,则关闭这个 http 连接
client_header_timeout 10; #客户端请求头的超时时间
client_body_timeout 10; #客户端请求主体超时时间
reset_timedout_connection on; #告诉 nginx 关闭不响应的客户端连接。这将会释放那个客户端所占有的内存空间
send_timeout 10; #客户端响应超时时间,在两次客户端读取操作之间。如果在这段时间内,客户端没有读取任何数据,nginx 就会关闭连接
################################

#FastCGI 相关参数是为了改善网站的性能:减少资源占用,提高访问速度。下面参数看字面意思都能理解。
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
fastcgi_buffer_size 64k;
fastcgi_buffers 4 64k;
fastcgi_busy_buffers_size 128k;
fastcgi_temp_file_write_size 128k;

###作为代理缓存服务器设置#######
###先写到 temp 再移动到 cache
#proxy_cache_path /var/tmp/nginx/proxy_cache levels=1:2 keys_zone=cache_one:512m inactive=10m max_size=64m;
###以上 proxy_temp 和 proxy_cache 需要在同一个分区中
###levels=1:2 表示缓存级别,表示缓存目录的第一级目录是 1 个字符,第二级目录是 2 个字符 keys_zone=cache_one:128m 缓存空间起名为 cache_one 大小为 512m
###max_size=64m 表示单个文件超过 128m 就不缓存了 inactive=10m 表示缓存的数据,10 分钟内没有被访问过就删除
#########end####################

#####对传输文件压缩###########
#gzip 模块设置
gzip on; #开启 gzip 压缩输出
gzip_min_length 1k; #最小压缩文件大小
gzip_buffers 4 16k; #压缩缓冲区
gzip_http_version 1.0; #压缩版本(默认 1.1,前端如果是 squid2.5 请使用 1.0)
gzip_comp_level 2; #压缩等级,gzip 压缩比,1 为最小,处理最快;9 为压缩比最大,处理最慢,传输速度最快,也最消耗 CPU;
gzip_types text/plain application/x-javascript text/css application/xml;
#压缩类型,默认就已经包含 text/html,所以下面就不用再写了,写上去也不会有问题,但是会有一个 warn。
gzip_vary on;
##############################

#limit_zone crawler $binary_remote_addr 10m; #开启限制 IP 连接数的时候需要使用

upstream blog.ha97.com {
#upstream 的负载均衡,weight 是权重,可以根据机器配置定义权重。weigth 参数表示权值,权值越高被分配到的几率越大。
server 192.168.80.121:80 weight=3;
server 192.168.80.122:80 weight=2;
server 192.168.80.123:80 weight=3;
}

#虚拟主机的配置
server {
#监听端口
listen 80;

#############https##################
#listen 443 ssl;
#ssl_certificate /opt/https/xxxxxx.crt;
#ssl_certificate_key /opt/https/xxxxxx.key;
#ssl_protocols SSLv3 TLSv1;
#ssl_ciphers HIGH:!ADH:!EXPORT57:RC4+RSA:+MEDIUM;
#ssl_prefer_server_ciphers on;
#ssl_session_cache shared:SSL:2m;
#ssl_session_timeout 5m;
####################################end

#域名可以有多个,用空格隔开
server_name www.ha97.com ha97.com;
index index.html index.htm index.php;
root /data/www/ha97;
location ~ .*.(php|php5)?$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi.conf;
}

#图片缓存时间设置
location ~ .*.(gif|jpg|jpeg|png|bmp|swf)$ {
expires 10d;
}

#JS 和 CSS 缓存时间设置
location ~ .*.(js|css)?$ {
expires 1h;
}

#日志格式设定
log_format access '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" $http_x_forwarded_for';

#定义本虚拟主机的访问日志
access_log /var/log/nginx/ha97access.log access;

#对 "/" 启用反向代理
location / {
proxy_pass http://127.0.0.1:88;
proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr;
#后端的 Web 服务器可以通过 X-Forwarded-For 获取用户真实 IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#以下是一些反向代理的配置,可选。
proxy_set_header Host $host;
client_max_body_size 10m; #允许客户端请求的最大单文件字节数
client_body_buffer_size 128k; #缓冲区代理缓冲用户端请求的最大字节数,

##代理设置 以下设置是 nginx 和后端服务器之间通讯的设置##
proxy_connect_timeout 90; #nginx 跟后端服务器连接超时时间(代理连接超时)
proxy_send_timeout 90; #后端服务器数据回传时间(代理发送超时)
proxy_read_timeout 90; #连接成功后,后端服务器响应时间(代理接收超时)
proxy_buffering on; #该指令开启从后端被代理服务器的响应内容缓冲 此参数开启后 proxy_buffers 和 proxy_busy_buffers_size 参数才会起作用
proxy_buffer_size 4k; #设置代理服务器(nginx)保存用户头信息的缓冲区大小
proxy_buffers 4 32k; #proxy_buffers 缓冲区,网页平均在 32k 以下的设置
proxy_busy_buffers_size 64k; #高负荷下缓冲大小(proxy_buffers*2)
proxy_max_temp_file_size 2048m; #默认 1024m, 该指令用于设置当网页内容大于 proxy_buffers 时,临时文件大小的最大值。如果文件大于这个值,它将从 upstream 服务器同步地传递请求,而不是缓冲到磁盘
proxy_temp_file_write_size 512k; 这是当被代理服务器的响应过大时 nginx 一次性写入临时文件的数据量。
proxy_temp_path /var/tmp/nginx/proxy_temp; ##定义缓冲存储目录,之前必须要先手动创建此目录
proxy_headers_hash_max_size 51200;
proxy_headers_hash_bucket_size 6400;
#######################################################
}

#设定查看 nginx 状态的地址
location /nginxStatus {
stub_status on;
access_log on;
auth_basic "nginxStatus";
auth_basic_user_file conf/htpasswd;
#htpasswd 文件的内容可以用 apache 提供的 htpasswd 工具来产生。
}

#本地动静分离反向代理配置
#所有 jsp 的页面均交由 tomcat 或 resin 处理
location ~ .(jsp|jspx|do)?$ {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:8080;
}

#所有静态文件由 nginx 直接读取不经过 tomcat 或 resin
location ~ .*.(htm|html|gif|jpg|jpeg|png|bmp|swf|ioc|rar|zip|txt|flv|mid|doc|ppt|pdf|xls|mp3|wma)$
{ expires 15d; }

location ~ .*.(js|css)?$
{ expires 1h; }
}
}

nginx 服务器基础配置指令

nginx.conf 文件的结构

  • Global: nginx 运行相关
  • events: 与用户的网络连接相关
  • http
    • http Global: 代理,缓存,日志,以及第三方模块的配置
    • server
      • server Global: 虚拟主机相关
      • location: 地址定向,数据缓存,应答控制,以及第三方模块的配置

所有的所有的所有的指令,都要以;结尾

nginx 运行相关的 Global 部分

配置运行 nginx 服务器用户

user nobody nobody;

配置允许生成的 worker process 数

worker_processes auto;
worker_processes 4;

这个数字,跟电脑 CPU 核数要保持一致

1
2
3
4
5
6
7
复制代码# grep ^proces /proc/cpuinfo
processor : 0
processor : 1
processor : 2
processor : 3
# grep ^proces /proc/cpuinfo | wc -l
4

配置 nginx 进程 PID 存放路径

pid logs/nginx.pid;

这里面保存的就是一个数字,nginx master 进程的进程号

配置错误日志的存放路径

error_log logs/error.log;
error_log logs/error.log error;

配置文件的引入

include mime.types;
include fastcgi_params;
include ../../conf/*.conf;

与用户的网络连接相关的 events

设置网络连接的序列化

accept_mutex on;

对多个 nginx 进程接收连接进行序列化,防止多个进程对连接的争抢(惊群)

设置是否允许同时接收多个网络连接

multi_accept off;

事件驱动模型的选择

use select|poll|kqueue|epoll|rtsig|/dev/poll|eventport

这个重点,后面再看

配置最大连接数

worker_connections 512;

http

http Global 代理 - 缓存 - 日志 - 第三方模块配置

定义 MIME-Type

include mime.types;
default_type application/octet-stream;

自定义服务日志

access_log logs/access.log main;
access_log off;

配置允许 sendfile 方式传输文件

sendfile off;

sendfile on;
sendfile_max_chunk 128k;

nginx 每个 worker process 每次调用 sendfile() 传输的数据量的最大值

Refer:

  • Linux kenel sendfile 如何提升性能
  • nginx sendifle tcp_nopush tcp_nodelay 参数解释

配置连接超时时间

与用户建立连接后,nginx 可以保持这些连接一段时间,默认 75s
下面的 65s 可以被 Mozilla/Konqueror 识别,是发给用户端的头部信息Keep-Alive值

keepalive_timeout 75s 65s;

单连接请求数上限

和用户端建立连接后,用户通过此连接发送请求;这条指令用于设置请求的上限数

keepalive_requests 100;

server

配置网络监听

listen *:80 | *:8000; # 监听所有的 80 和 8000 端口

listen 192.168.1.10:8000;
listen 192.168.1.10;
listen 8000; # 等同于 listen *:8000;
listen 192.168.1.10 default_server backlog=511; # 该 ip 的连接请求默认由此虚拟主机处理;最多允许 1024 个网络连接同时处于挂起状态

基于名称的虚拟主机配置

server_name myserver.com www.myserver.com;

server_name .myserver.com www.myserver. myserver2.*; # 使用通配符

不允许的情况: server_name www.ab\*d.com; # *只允许出现在 www 和 com 的位置

server_name ~^www\d+.myserver.com$; # 使用正则

nginx 的配置中,可以用正则的地方,都以~开头

from nginx~0.7.40 开始,server_name 中的正则支持 字符串捕获功能(capture)

server_name ~^www.(.+).com$; # 当请求通过 www.myserver.com 请求时, myserver 就被记录到$1中,在本 server 的上下文中就可以使用

如果一个名称 被多个虚拟主机的 server_name 匹配成功,那这个请求到底交给谁处理呢?看优先级:

  1. 准确匹配到 server_name
  2. 通配符在开始时匹配到 server_name
  3. 通配符在结尾时匹配到 server_name
  4. 正则表达式匹配 server_name
  5. 先到先得

配置 https 证书

原理

https 是在 http 和 TCP 中间加上一层加密层

  • 浏览器向服务端发送消息时:本质上是浏览器(客户端)使用服务端的公钥来加密信息,服务端使用自己的私钥解密,
  • 浏览器从服务端获取消息是:服务端使用自己私钥加密,浏览器(客户端)使用服务端的公钥来解密信息

在这个过程中,需要保证服务端给浏览器的公钥不是假冒的。证明服务端公钥信息的机构是 CA(数字认证中心)

可以理解为:如果想证明一个人的身份是真的,就得证明这个人的身份证是真的

数字证书

1
2
3
复制代码数字证书相当于物理世界中的身份证,
在网络中传递信息的双方互相不能见面,利用数字证书可确认双方身份,而不是他人冒充的。
这个数字证书由信任的第三方,即认证中心使用自己的私钥对 A 的公钥加密,加密后文件就是网络上的身份证了,即数字证书

大致可以理解为如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
复制代码1. 服务端将自己的公钥和其他信息(服务端数字证书),请求数字认证中心签名,数字认证中心使用自己的私钥在证书里加密(只有数字认证中心的公钥才能解开)
2. 服务端将自己的证书(证书里面包括服务端的公钥)给浏览器
3. 浏览器的“证书管理器”中有“受信任的根证书颁发机构”列表,客户端在接收到响应后,会在这个列表里查看是否存在解开该服务器数字证书的公钥。有两种错误情况:如果公钥在这个列表里,但是解码后的内容不匹配,说明证书被冒用;如果公钥不在这个列表里,说明这张证书不是受信任的机构所颁发,他的真实性无法确定
4. 如果一切都没问题,浏览器就可以使用服务器的公钥对信息内容进行加密,然后与服务器交换信息(已加密)

+--------------+ +------------------+
| 服务端 |---------->| 数字认证中心 (CA) |
+------+-------+ 1 X +------------------+
| / /
| / /
| / /
| / /
|2 3 / / 4
| / /
| / /
| / /
X / /
+--------------+ /
| 浏览器 |X
+--------------+

只要证书(证书里有服务端的公钥)是可信的,公钥就是可信的。

证书格式

Linux 下的工具们通常使用 base64 编码的文本格式,相关常用后缀如下

  • 证书
    • .crt
    • .pem
    • .cer(IIS 等一些平台下,则习惯用 cer 作为证书文件的扩展名,二进制证书)
  • 私钥:.key
  • 证书请求:.csr
  • 其他
    • .keystore java 密钥库(包括证书和私钥)

制作证书

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
复制代码1. 生成服务器端的私钥 (key 文件)
$openssl genrsa -out server.key 1024

2. 生成服务器端证书签名请求文件 (csr 文件);
$ openssl req -new -key server.key -out server.csr

...
Country Name:CN------------ 证书持有者所在国家
State or Province Name:BJ-- 证书持有者所在州或省份(可省略不填)
Locality Name:BJ----------- 证书持有者所在城市(可省略不填)
Organization Name:SC------- 证书持有者所属组织或公司
Organizational Unit Name:.- 证书持有者所属部门(可省略不填)
Common Name :ceshi.com----- 域名
Email Address:------------- 邮箱(可省略不填)

A challenge password:------ 直接回车
An optional company name:-- 直接回车


3. 生成证书文件 (crt 文件)
$ openssl x509 -req -days 1000 -in server.csr -signkey server.key -out server.crt

以上生成 server.crt server.key 文件即是用于 HTTPS 配置的证书和 key

如果想查看证书里面的内容,可以通过 $openssl x509 -in server.crt -text -noout 查看

配置 nginx

在 nginx 的 server 区域内添加如下

1
2
3
4
5
6
7
8
复制代码listen 443 ssl;
ssl_certificate /opt/https/server.crt;
ssl_certificate_key /opt/https/server.key;
ssl_protocols SSLv3 TLSv1;
ssl_ciphers HIGH:!ADH:!EXPORT57:RC4+RSA:+MEDIUM;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:2m;
ssl_session_timeout 5m;

基于 IP 的虚拟主机配置

基于 IP 的虚拟主机,需要将网卡设置为同时能够监听多个 IP 地址

1
2
3
4
5
6
7
8
9
10
复制代码ifconfig
# 查看到本机 IP 地址为 192.168.1.30
ifconfig eth1:0 192.168.1.31 netmask 255.255.255.0 up
ifconfig eth1:1 192.168.1.32 netmask 255.255.255.0 up
ifconfig
# 这时就看到 eth1 增加来 2 个别名, eth1:0 eth1:1

# 如果需要机器重启后仍保持这两个虚拟的 IP
echo "ifconfig eth1:0 192.168.1.31 netmask 255.255.255.0 up" >> /etc/rc.local
echo "ifconfig eth1:0 192.168.1.32 netmask 255.255.255.0 up" >> /etc/rc.local

再来配置基于 IP 的虚拟主机

1
2
3
4
5
6
7
8
9
10
11
12
13
复制代码http {
...
server {
listen 80;
server_name 192.168.1.31;
...
}
server {
listen 80;
server_name 192.168.1.32;
...
}
}

配置 location 块

location 块的配置,应该是最常用的了

location [ = | ~ | * | ^ ] uri {…}

这里内容分 2 块,匹配方式和 uri, 其中 uri 又分为 标准 uri 和正则 uri

先不考虑 那 4 种匹配方式

  1. nginx 首先会再 server 块的多个 location 中搜索是否有标准 uri和请求字符串匹配, 如果有,记录匹配度最高的一个;
  2. 然后,再用 location 块中的正则 uri和请求字符串匹配, 当第一个正则 uri匹配成功,即停止搜索, 并使用该 location 块处理请求;
  3. 如果,所有的正则 uri都匹配失败,就使用刚记录下的匹配度最高的一个标准 uri处理请求
  4. 如果都失败了,那就失败喽

再看 4 种匹配方式:

  • =: 用于标准 uri前,要求请求字符串与其严格匹配,成功则立即处理
  • ^~: 用于标准 uri前,并要求一旦匹配到,立即处理,不再去匹配其他的那些个正则 uri
  • ~: 用于正则 uri前,表示 uri 包含正则表达式, 并区分大小写
  • ~*: 用于正则 uri前, 表示 uri 包含正则表达式, 不区分大小写

^~ 也是支持浏览器编码过的 URI 的匹配的哦, 如 /html/%20/data 可以成功匹配 /html/ /data

[root] 配置请求的根目录

Web 服务器收到请求后,首先要在服务端指定的目录中寻找请求资源

1
复制代码root /var/www;

root 后跟的指定目录是上级目录

该上级目录下要含有和 location 后指定名称的同名目录才行,末尾“/”加不加无所谓

1
2
3
复制代码location /c/ {
root /a/
}

访问站点 http://location/c 访问的就是 /a/c 目录下的站点信息。

[alias] 更改 location 的 URI

除了使用 root 指明处理请求的根目录,还可以使用 alias 改变 location 收到的 URI 的请求路径

1
2
3
复制代码location ~ ^/data/(.+\.(htm|html))$ {
alias /locatinotest1/other/$1;
}

alias 后跟的指定目录是准确的,并且末尾必须加“/”,否则找不到文件

1
2
3
复制代码location /c/ {
alias /a/
}

访问站点 http://location/c 访问的就是 /a/ 目录下的站点信息。

【注】一般情况下,在 location / 中配置 root,在 location /other 中配置 alias 是一个好习惯。

设置网站的默认首页

index 指令主要有 2 个作用:

  • 对请求地址没有指明首页的,指定默认首页
  • 对一个请求,根据请求内容而设置不同的首页,如下:
1
2
3
复制代码location ~ ^/data/(.+)/web/$ {
index index.$1.html index.htm;
}

设置网站的错误页面

error_page 404 /404.html;
error_page 403 /forbidden.html;
error_page 404 =301 /404.html;

1
2
3
复制代码location /404.html {
root /myserver/errorpages/;
}

基于 IP 配置 nginx 的访问权限

1
2
3
4
5
6
复制代码location / {
deny 192.168.1.1;
allow 192.168.1.0/24;
allow 192.168.1.2/24;
deny all;
}

从 192.168.1.0 的用户时可以访问的,因为解析到 allow 那一行之后就停止解析了

基于密码配置 nginx 的访问权限

auth_basic “please login”;
auth_basic_user_file /etc/nginx/conf/pass_file;

这里的 file 必须使用绝对路径,使用相对路径无效

1
2
3
4
5
复制代码# /usr/local/apache2/bin/htpasswd -c -d pass_file user_name
# 回车输入密码,-c 表示生成文件,-d 是以 crypt 加密。

name1:password1
name2:password2:comment

经过 basic auth 认证之后没有过期时间,直到该页面关闭;
如果需要更多的控制,可以使用 HttpAuthDigestModule wiki.nginx.org/HttpAuthDig…

应用

架设简单文件服务器

将 /data/public/ 目录下的文件通过 nginx 提供给外部访问

1
2
复制代码#mkdir /data/public/
#chmod 777 /data/public/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
复制代码worker_processes 1;
error_log logs/error.log info;
events {
use epoll;
}
http {
server {
# 监听 8080 端口
listen 8080;
location /share/ {
# 打开自动列表功能,通常关闭
autoindex on;
# 将 /share/ 路径映射至 /data/public/,请保证 nginx 进程有权限访问 /data/public/
alias /data/public/;
}
}
}

nginx 正向代理

  • 正向代理指代理客户端访问服务器的一个中介服务器,代理的对象是客户端。正向代理就是代理服务器替客户端去访问目标服务器
  • 反向代理指代理后端服务器响应客户端请求的一个中介服务器,代理的对象是服务器。
  1. 配置

代理服务器配置

nginx.conf

1
2
3
4
5
6
7
8
9
复制代码server{
resolver x.x.x.x;
# resolver 8.8.8.8;
listen 82;
location / {
proxy_pass http://$http_host$request_uri;
}
access_log /data/httplogs/proxy-$host-aceess.log;
}

location 保持原样即可,根据自己的配置更改 listen port 和 dnf 即 resolver
验证:
在需要访问外网的机器上执行以下操作之一即可:

1
2
3
复制代码1. export http_proxy=http://yourproxyaddress:proxyport(建议)
2. vim ~/.bashrc
export http_proxy=http://yourproxyaddress:proxyport

2 不足
nginx 不支持 CONNECT 方法,不像我们平时用的 GET 或者 POST,可以选用 apache 或 squid 作为代替方案。

nginx 服务器基础配置实例

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
复制代码user nginx nginx;

worker_processes 3;

error_log logs/error.log;
pid myweb/nginx.pid;

events {
use epoll;
worker_connections 1024;
}

http {
include mime.types;
default_type applicatioin/octet-stream;

sendfile on;

keepalive_timeout 65;

log_format access.log '$remote_addr [$time_local] "$request" "$http_user_agent"';

server {
listen 8081;
server_name myServer1;

access_log myweb/server1/log/access.log;
error_page 404 /404.html;

location /server1/location1 {
root myweb;
index index.svr1-loc1.htm;
}

location /server1/location2 {
root myweb;
index index.svr1-loc2.htm;
}
}

server {
listen 8082;
server_name 192.168.0.254;

auth_basic "please Login:";
auth_basic_user_file /opt/X_nginx/nginx/myweb/user_passwd;

access_log myweb/server2/log/access.log;
error_page 404 /404.html;

location /server2/location1 {
root myweb;
index index.svr2-loc1.htm;
}

location /svr2/loc2 {
alias myweb/server2/location2/;
index index.svr2-loc2.htm;
}

location = /404.html {
root myweb/;
index 404.html;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
复制代码#./sbin/nginx -c conf/nginx02.conf
nginx: [warn] the "user" directive makes sense only if the master process runs with super-user privileges, ignored in /opt/X_nginx/nginx/conf/nginx02.conf:1
.
├── 404.html
├── server1
│ ├── location1
│ │ └── index.svr1-loc1.htm
│ ├── location2
│ │ └── index.svr1-loc2.htm
│ └── log
│ └── access.log
└── server2
├── location1
│ └── index.svr2-loc1.htm
├── location2
│ └── index.svr2-loc2.htm
└── log
└── access.log

8 directories, 7 files

测试 myServer1 的访问

1
2
3
4
5
复制代码http://myserver1:8081/server1/location1/
this is server1/location1/index.svr1-loc1.htm

http://myserver1:8081/server1/location2/
this is server1/location1/index.svr1-loc2.htm

测试 myServer2 的访问

1
2
3
4
5
6
7
8
复制代码http://192.168.0.254:8082/server2/location1/
this is server2/location1/index.svr2-loc1.htm

http://192.168.0.254:8082/svr2/loc2/
this is server2/location1/index.svr2-loc2.htm

http://192.168.0.254:8082/server2/location2/
404 404 404 404

使用缓存

创建缓存目录

1
2
复制代码mkdir  /tmp/nginx_proxy_cache2
chmod 777 /tmp/nginx_proxy_cache2

修改配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
复制代码# http 区域下添加缓存区配置
proxy_cache_path /tmp/nginx_proxy_cache2 levels=1 keys_zone=cache_one:512m inactive=60s max_size=1000m;

# server 区域下添加缓存配置
#缓存相应的文件(静态文件)
location ~ \.(gif|jpg|png|htm|html|css|js|flv|ico|swf)(.*) {
proxy_pass http://IP: 端口;#如果没有缓存则通过 proxy_pass 转向请求
proxy_redirect off;
proxy_set_header Host $host;
proxy_cache cache_one;
proxy_cache_valid 200 302 1h; #对不同的 HTTP 状态码设置不同的缓存时间,h 小时,d 天数
proxy_cache_valid 301 1d;
proxy_cache_valid any 1m;
expires 30d;
}

使用 location 反向代理到已有网站

1
2
3
复制代码location ~/bianque/(.*)$ {
proxy_pass http://127.0.0.1:8888/$1/?$args;
}
  • 加内置变量 ![args 是保障 nginx 正则捕获 get 请求时不丢失,如果只是 post 请求,](https://gitee.com/songjianzaina/juejin_p15/raw/master/img/613bb1fea330b5e755cd61dd0f01c5c14ce79ac188ebbad0f7d4404e32c357eb)args是非必须的
  • $1 取自正则表达式部分()里的内容

其他

ngx_http_sub_module 替换响应中内容

  • ngx_http_sub_module nginx 用来替换响应内容的一个模块(应用:有些程序中写死了端口,可以通过此工具将页面中的端口替换为其他端口)

配置 http 强制跳转 https

在 nginx 配置文件中的 server 区域添加如下内容

1
2
3
复制代码if ($scheme = 'http') {
rewrite ^(.*)$ https://$host$uri;
}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

SQL Server(MSSQLSERVER) 请求失败或服

发表于 2019-09-18

转自:www.fengjunzi.com/blog-25573.…

问题

有时候sqlserver无法启动了,原因是mssqlserver服务没有启动,当你手动启动时,又出现服务无法响应的可恶错误提示。。。

笔者“有幸”遇到了,我的原因是第5个,禁用TCP/IP协议,现在总结下可能的原因及解决方案

解决方案

IP地址配置不正确:

打开 Microsoft SQL Server 2005配置工具下的SQL Server Configuration Manager,选择MSSQLSERVER协议, 然后双击右边窗口的TCP/IP,在弹出窗口中检查IP配置。

可能是因为VIA协议启用造成的。解决方法如下:

1)打开SQL Server 2008中的配置工具SQL Server Configure Manager,将VIA协议禁用.2)重新启动SQL Server(MSSQLSERVER ),成功.

管理员密码修改也会造成sqlserver服务无法启动。解决方法如下:

打开 Microsoft SQL Server 2005配置工具下的SQL Server Configuration Manager,在MSSQLSERVER服务属性中,修改以哪个账号来启动服务。我机器启动不了服务的原因就是启用了 “VIA”服务,禁用后,OK了。

安装的是SQL Server 评估版,180天的试用期后,MSSQLSERVER服务就无法启动,手动启动就报告17051错误。

解决办法:

  • 第一步:进入SQL2008配置工具中的安装中心,
  • 第二步:再进入维护界面,选择版本升级,
  • 第三步:进入产品密钥,输入密钥

Developer: PTTFM-X467G-P7RH2-3Q6CG-4DMYB

Enterprise: JD8Y6-HQG69-P9H84-XDTPG-34MBB

  • 第四步:一直点下一步,直到升级完毕。
    用key升级成功后即可启动MSSQLSERVER服务。如果启动SQL SERVER管理器依然报告过期错误,则将注册表HKEYLOCALMACHINESOFTWAREMicrosoftMicrosoft SQL Server100ConfigurationState,将其中CommonFiles的键值改为3。
    然后再重复以上四个步骤,进行升级就OK了。

禁用TCP/IP协议。

问题的根源

上边写的解决方案,只能把问题解决,但是根本不了解为什么要这样做,有可能遇到同样的问题,这几个解决方案也不好使,最主要得知道为什么报错,到底是哪的原因,报错提示也说了==有关详细信息请参见事件日志或其他适用的错误日志==,那么日志在哪找呢问题又来了。

错误日志在哪

第一步

  • 在桌面“计算机”图标上面点击右键,选择“管理”。
  • 在计算机管理界面选择“事件查看器”。
  • 进入事件查看器界面,在此可查看系统事件日志。
  • 点击windows日志。
  • 双击应用程序日志。
  • 将应用程序日志展开后可以查询到详细信息。

第二步

  • 根据系统提供的日志信息
  • 打开查看,启动失败之后日志有一些信息(包括错误)

此处是禁用TCP/IP 协议,有的会提示端口被占用,把SQL Server (SQLEXPRESS)服务停掉或者改个端口就可以。如果报的是Named Pipes和VIA的错,就把这两个禁用,

其他的错,根据错误信息提示去排除。

禁用协议在哪里

  • 打开Sql Server Configuration Manager
  • 打开SQL Sever网络配置
  • 打开MSSQLSERVER就看到TCP/IP协议了

禁用后就可以启动MSSQLSERVER服务了。

————————————————

版权声明:本文为CSDN博主「于云秀」的原创文章原文链接:https://blog.csdn.net/yyx3214/article/details/78710963

欢迎访问我的个人网站风君子博客,微信fj3702交流,请注明来源

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Java并发线程池到底设置多大?

发表于 2019-09-17

前言

在我们日常业务开发过程中,或多或少都会用到并发的功能。那么在用到并发功能的过程中,就肯定会碰到下面这个问题并发线程池到底设置多大呢?通常有点年纪的程序员或许都听说这样一个说法 (其中 N 代表 CPU 的个数)1. CPU 密集型应用,线程池大小设置为 N + 1
2. IO 密集型应用,线程池大小设置为 2N

这个说法到底是不是正确的呢?其实这是极不正确的。那为什么呢?首先我们从反面来看,假设这个说法是成立的,那我们在一台服务器上部署多少个服务都无所谓了。因为线程池的大小只能服务器的核数有关,所以这个说法是不正确的。那具体应该怎么设置大小呢?假设这个应用是两者混合型的,其中任务即有 CPU 密集,也有 IO 密集型的,那么我们改怎么设置呢?是不是只能抛硬盘来决定呢?那么我们到底该怎么设置线程池大小呢?有没有一些具体实践方法来指导大家落地呢?让我们来深入地了解一下。Little’s Law(利特尔法则)一个系统请求数等于请求的到达率与平均每个单独请求花费的时间之乘积假设服务器单核的,对应业务需要保证请求量(QPS):10 ,真正处理一个请求需要 1 秒,那么服务器每个时刻都有 10 个请求在处理,即需要 10 个线程同样,我们可以使用利特尔法则(Little’s law)来判定线程池大小。我们只需计算请求到达率和请求处理的平均时间。然后,将上述值放到利特尔法则(Little’s law)就可以算出系统平均请求数。估算公式如下*线程池大小 = ((线程 IO time + 线程 CPU time )/线程 CPU time ) CPU数目**具体实践

通过公式,我们了解到需要 3 个具体数值1. 一个请求所消耗的时间 (线程 IO time + 线程 CPU time)
2. 该请求计算时间 (线程 CPU time)
3. CPU 数目

请求消耗时间

Web 服务容器中,可以通过 Filter 来拦截获取该请求前后消耗的时间

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
复制代码public class MoniterFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(MoniterFilter.class);
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
ServletException {
long start = System.currentTimeMillis();
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String uri = httpRequest.getRequestURI();
String params = getQueryString(httpRequest);
try {
chain.doFilter(httpRequest, httpResponse);
} finally {
long cost = System.currentTimeMillis() - start;
logger.info("access url [{}{}], cost time [{}] ms )", uri, params, cost);
}
private String getQueryString(HttpServletRequest req) {
StringBuilder buffer = new StringBuilder("?");
Enumeration<String> emParams = req.getParameterNames();
try {
while (emParams.hasMoreElements()) {
String sParam = emParams.nextElement();
String sValues = req.getParameter(sParam);
buffer.append(sParam).append("=").append(sValues).append("&");
}
return buffer.substring(0, buffer.length() - 1);
} catch (Exception e) {
logger.error("get post arguments error", buffer.toString());
}
return "";
}
}

CPU 计算时间

CPU 计算时间 = 请求总耗时 - CPU IO time假设该请求有一个查询 DB 的操作,只要知道这个查询 DB 的耗时(CPU IO time),计算的时间不就出来了嘛,我们看一下怎么才能简洁,明了的记录 DB 查询的耗时。通过(JDK 动态代理/ CGLIB)的方式添加 AOP 切面,来获取线程 IO 耗时。代码如下,请参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
复制代码public class DaoInterceptor implements MethodInterceptor {
private static final Logger logger = LoggerFactory.getLogger(DaoInterceptor.class);
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
StopWatch watch = new StopWatch();
watch.start();
Object result = null;
Throwable t = null;
try {
result = invocation.proceed();
} catch (Throwable e) {
t = e == null ? null : e.getCause();
throw e;
} finally {
watch.stop();
logger.info("({}ms)", watch.getTotalTimeMillis());
}
return result;
}
}

CPU 数目

逻辑 CPU 个数 ,设置线程池大小的时候参考的 CPU 个数

1
复制代码cat /proc/cpuinfo| grep "processor"| wc -l

总结

合适的配置线程池大小其实很不容易,但是通过上述的公式和具体代码,我们就能快速、落地的算出这个线程池该设置的多大。不过最后的最后,我们还是需要通过压力测试来进行微调,只有经过压测测试的检验,我们才能最终保证的配置大小是准确的。最后

欢迎大家关注我的公众号【程序员追风】,文章都会在里面更新,整理的资料也会放在里面。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

服务器必备基础配置(Ubuntu) 本文目的 配置VPN I

发表于 2019-09-17

本文目的

  • 记录真实环境中服务器必须配置;

配置VPN

推荐使用L2TP或IPSec方式建立VPN,成熟的标准方案,拥有较好的安全性。

  • L2TP:单台管理终端或移动终端;
  • IPSec:多台固定的管理终端一般在入口路由器上配置IPSec实现互联。

IPMI管理口配置

当前市场上的服务器一般都支持管理口来对服务器进行管理,利用管理口功能,我们可以远程实现系统的开关机、重启、配置更改、安装系统等,基本能使我们不再需要申请进入电信机房来解决各种问题。
配置管理口时需要注意以下问题:

  1. 管理口配置为静态IP
  2. 去掉默认帐户,使用独有的用户名和密码

Ubuntu基本配置

  1. 更新源和系统软件

1
2
3
4
复制代码sudo mv /etc/apt/sources.list /etc/apt/sources.list.bak
sudo vim /etc/apt/sources.list # 添加源,推荐使用国内阿里/网易相应的源
sudo apt-get update 或 sudo apt update
sudo apt-get upgrade 或 sudo apt upgrade
  1. 网络及DNS配置

Ubuntu 16.04 及之前版本
配置静态IP sudo vim /etc/network/interface

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
复制代码# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

# The loopback network interface
auto lo
iface lo inet loopback

#auto eth0
#iface eth0 inet dhcp

auto eth0
iface eth0 inet static
address 192.168.1.100
netmask 255.255.255.0
gateway 192.168.1.1

配置本机hosts sudo vim /etc/hosts
注释这一行:127.0.1.1 ubuntu
修改机器名或添加内网地址,不要让机器名解析为回路地址

配置 DNS 服务器地址 sudo vim /etc/resolvconf/resolv.conf.d/base
添加:nameserver 192.168.1.114

Ubuntu 18.04 采用 netplan 作为网络配置管理,与16.04及之前的版本区别很大
去掉文件 /etc/network/interfaces 中的所有配置
修改配置文件:sudo vim /etc/netplan/50-cloud-init.yaml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
复制代码# This file is generated from information provided by
# the datasource. Changes to it will not persist across an instance.
# To disable cloud-init's network configuration capabilities, write a file
# /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg with the following:
# network: {config: disabled}
network:
ethernets:
eth0:
addresses:
- 192.168.1.100/24
gateway4: 192.168.1.1
nameservers:
addresses:
- 192.168.1.114
search:
- corp.xxx.com
version: 2

然后运行命令使配置立即生效:sudo netplan apply

附:如需在本地配置专用 DNS 服务器,可参考 内网配置DHCP+DNS(dnsmasq)

  1. SSH安全配置

机器只允许内部IP地址SSH进去,只允许指定用户进行SSH,其他用户都不允许SSH
/etc/hosts.allow添加 sshd:192.168.2.,192.168.3.10:allow
/etc/hosts.deny添加 sshd : ALL
/etc/ssh/sshd_config
禁用root登陆,修改:PermitRootLogin no
添加允许登陆的用户:AllowUsers username username为允许登陆的用户名
hosts.allow, hosts.deny修改后立即生效
sshd_config修改后需要重启ssh服务:sudo service ssh restart, 重启不会断开当前连接

  1. Ubuntu开机启动

Ubuntu需要开机启动的命令添加到/etc/rc.local中

  1. 配置NTP时间服务器

自动与Ubuntu时间服务器同步,并且本机可以作为时间服务器使用,需要等待几分钟后才可以同步到最新:
$ sudo apt-get install ntp
安装之后ntp自动启动

  1. 优化内存使用(内存足够时推荐禁用交换分区)

swappiness值的大小决定如何使用swap分区,系统默认为60,也就是说,你的内存在使用到100-60=40%的时候,就开始出现有交换分区的使用。

1
2
3
4
5
6
复制代码cat /proc/sys/vm/swappiness  # 查看swapness:
sysctl vm.swappiness=5 # 这只是临时调整的方法,重启后会回到默认设置的
# 要想永久调整的话,需要将/etc/sysctl.conf修改:
sudo vim /etc/sysctl.conf
添加:vm.swappiness=5
执行:sudo sysctl -p 立即生效
  1. 禁用交换分区

命令:swapon -s 查看交换分区
通过命令: swapoff -a 禁用交换分区。将该命令命令添加到”/etc/rc.local”中,开机启动时就禁用交换分区。
要永久禁掉swap分区,打开文件/etc/fstab注释掉swap那一行
建议:在所有机器上禁用交换分区

  1. 多网卡bond配置

现在服务器一般都不止一个网口,通过bond将多个网口合并成一个逻辑网口,能够充分利用多个网口的带宽。
bond的七种模式,bond=0~6

  1. mode=0(balance-rr)(平衡抡循环策略)
  2. mode=1(active-backup)(主-备份策略)
  3. mode=2(balance-xor)(平衡策略)
  4. mode=3(broadcast)(广播策略)
  5. mode=4(802.3ad)(IEEE 802.3ad 动态链接聚合)
  6. mode=5(balance-tlb)(适配器传输负载均衡)
  7. mode=6(balance-alb)(适配器适应性负载均衡)

常用的有三种
mode=0:平衡负载模式,有自动备援,但需要”Switch”支援及设定。
mode=1:自动备援模式,其中一条线若断线,其他线路将会自动备援。
mode=6:平衡负载模式,有自动备援,不必”Switch”支援及设定。

查看是否支持bond: # modinfo bonding |more

(1) Ubuntu 14.04 bond 配置

安装 $ sudo apt-get install ifenslave
修改文件:sudo vim /etc/modules 添加:bonding mode=6 miimon=100
修改文件:sudo vim /etc/network/interfaces

1
2
3
4
5
6
7
8
9
10
11
12
13
14
复制代码# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

# The loopback network interface
auto lo
iface lo inet loopback

auto bond0
iface bond0 inet static
address 192.168.1.10
netmask 255.255.255.0
gateway 192.168.1.1
post-up ifenslave bond0 eth0 eth1
pre-down ifenslave -d bond0 eth0 eth1

(2) Ubuntu 16.04配置

与14.04基本相同,唯一的区别在/etc/network/interfaces
首先还是安装:$ sudo apt-get install ifenslave
修改文件:sudo vim /etc/modules 添加:bonding mode=6 miimon=100
修改文件:sudo vim /etc/network/interfaces

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
复制代码# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

#source /etc/network/interfaces.d/*

# The loopback network interface
auto lo
iface lo inet loopback

auto eth0
iface eth0 inet manual
bond-master bond0

auto eth1
iface eth1 inet manual
bond-master bond0

auto bond0
iface bond0 inet static
address 192.168.1.10
netmask 255.255.255.0
gateway 192.168.1.1
bond-slaves eth0 eth1
bond-lacp-rate 1
bond-mode 6
bond-miimon 100

(3) Ubuntu 18.04配置

Ubuntu 18.04 采用netplan作为网络配置管理,与16.04及之前的版本区别很大
去掉文件 /etc/network/interfaces 中的所有配置
修改配置文件:sudo vim /etc/netplan/50-cloud-init.yaml:

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
复制代码# This file is generated from information provided by
# the datasource. Changes to it will not persist across an instance.
# To disable cloud-init's network configuration capabilities, write a file
# /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg with the following:
# network: {config: disabled}
network:
bonds:
bond0:
addresses:
- 192.168.1.100/24
gateway4: 192.168.1.1
interfaces:
- eth0
- eth1
nameservers:
addresses:
- 192.168.1.114
search:
- corp.xxx.com
parameters:
mode: balance-alb
ethernets:
eth0: {}
eth1: {}
version: 2

然后运行命令使配置立即生效:sudo netplan apply

  1. keepalived配置虚拟IP

一般服务推荐使用虚拟IP,方便配置备用服务器
安装:sudo apt-get install keepalived
编辑配置文件:sudo vim /etc/keepalived/keepalived.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
复制代码vrrp_instance VI_1 {
state MASTER
interface em2
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass 51
}
virtual_ipaddress {
192.168.2.4/24
192.168.2.5/24
}
}

其中:priority表示优先级,高优先级的服务器作为MASTER,低优先级的作为SLAVE
查看虚拟IP:ip addr

  1. Ubuntu LVM扩容

  1. 查看磁盘设备:ls /dev/sd*,新添加三块磁盘为例
    显示:/dev/sda /dev/sda1 /dev/sda2 /dev/sda3 /dev/sdb /dev/sdc /dev/sdd
    其中sdb,sdc,sdd为三个新磁盘
  2. 磁盘上创建物理分区:fdisk /dev/sdb
    p 查看分区表,使用 gpt 分区表(Disklabel type),gpt支持大磁盘
    n 创建分区,都使用默认值
    t 更改分区类型,使用 Linux LVM
    w 保存更改并退出
    其他磁盘类似
  3. 创建物理卷:pvcreate /dev/sdd1 或 pvcreate /dev/sd[b,c,d]1直接创建所有物理卷
  4. 将物理卷添加到卷组,使用vgdisplay查看卷组
    vgextend host-vg /dev/sdb1 或
    vgextend host-vg /dev/sdb1 /dev/sdc1 /dev/sdd1
  5. 对逻辑卷扩容:lvextend -l +100%FREE /dev/host-vg/root
  6. 让系统重新识别:resize2fs /dev/host-vg/root
  1. Ubuntu挂载U盘

  1. 插入U盘,之后需要设置挂载点。
  2. 首先运行命令fdisk -l查看磁盘信息,由信息可得其路径“/dev/sdb1”,磁盘格式为fat32.
  3. 运行挂载命令:mount -t vfat /dev/sdb1 /media
  4. 运行命令cd media进入挂载U盘的主目录。
  5. 运行ls命令可以查看目录下的文件。
添加新磁盘并挂载到指定目录

创建分区 sudo fdisk /dev/sda
格式化 sudo mkfs.ext4 /dev/sda1
挂载到对应目录 sudo mount /dev/sda1 /mnt

  1. 安装docker

  1. DaoCloud快速安装docker:get.daocloud.io/#install-do…
  2. 让docker命令不再需要sudo
1
2
3
4
复制代码# 添加当前用户到docker组
sudo usermod -aG docker $USER
#切换当前会话到新 group 或者重启 X 会话
newgrp - docker 或 pkill X
  1. 解决docker pull默认使用https问题
1
2
3
4
5
6
7
复制代码# Ubuntu 14.04
sudo vim /etc/default/docker, 添加
DOCKER_OPTS="--insecure-registry 0.0.0.0/0"
# Ubuntu 16.04
sudo vim /etc/docker/daemon.json, 添加
{"insecure-registries":["0.0.0.0/0"]}
# 之后重启docker服务
  1. 配置本地 docker Hub

1
2
3
4
5
6
7
复制代码docker run -d --restart=always --name myDockerHub \
-p 5000:5000 \
-v /home/${USER}/docker-registry:/opt/docker-image \
-e SQLALCHEMY_INDEX_DATABASE=sqlite:////opt/docker-image/docker-registry.db \
-e STORAGE_PATH=/opt/docker-image/data registry:0.9.0

echo -e "\033[32;49;1mMy-Docker-Hub started......\033[39;49;0m"
  1. 禁用IPv6

修改 /etc/sysctl.conf 文件,添加:

1
2
3
4
复制代码net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1
net.ipv6.conf.eth0.disable_ipv6 = 1

然后运行:sudo sysctl -p,就可以用 ifconfig 或 ip a 命令查看是否没有 IPv6 地址了

  1. 安装Linux-dash系统监视工具

Linux-dash 是一个低开销 Linux 服务器监控系统,基于 Web 的监控界面。

  1. 安装apache2: apt-get install apache2
  2. 安装php5: apt-get install php5
  3. 修改apache2的端口:vim /etc/apache2/ports.conf
  4. 下载Linux-dash:
    cd /var/www/html/
    git clone https://github.com/afaqurk/linux-dash.git
  5. 重启apache2:sudo service apache2 restart
  6. 通过浏览器登陆监视页面: http://ip:port/linux-dash

附

  • 检测网站安全性:SSL Server Test
  • LVM逻辑卷管理器配置使用详解
  • LVM (简体中文)
  • Docker 的安装包以及周边高速镜像
  • Bonding documents
  • ubuntu18.04 开机启动/停止服务

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

史上最详 Thymeleaf 使用教程

发表于 2019-09-17

前言

操作前建议先参考我的另一篇博客:玩转 SpringBoot 2 快速整合 | Thymeleaf 篇 查看如何在SpringBoot 中使用 Thymeleaf。还有一点需要注意的是:模版页面中的 html 上需要声明 Thymeleaf 的命名空间,具体代码如下:

1
复制代码<html xmlns:th="http://www.thymeleaf.org">

接下来就可以开始 Thymeleaf 使用教程了!

全文介绍 Thymeleaf 是基于 Thymeleaf 3.0.11.RELEASE 版本进行说明的。

基础语法

文本标签 th:text/th:utext

用于文本内容的显示操作。

  1. th:text 进行文本替换 不会解析html
  2. th:utext 进行文本替换 会解析html

代码演示:

1
2
3
4
5
6
复制代码    @RequestMapping("/th")
public String th(Model model){
String msg = "<h1>我是h1</h1>";
model.addAttribute("msg",msg);
return "/course/th";
}

th:text 进行文本替换 不会解析html

1
复制代码<p th:text="text标签:  + ${msg}"></p>

结果页面:

1
复制代码<p>text标签:<h1>我是h1</h1></p>

游览器访问的效果:在这里插入图片描述

th:utext 进行文本替换 会解析html

1
复制代码<p th:utext="utext标签: + ${msg}"></p>

游览器展示效果如下图:在这里插入图片描述使用 + 和 | | 效果是一样的,如下代码所示:

1
2
复制代码<p th:utext="utext标签: + ${msg}"></p>
<p th:utext="|utext标签: ${msg}|"></p>

字符串拼接

拼接字符串通过 + 或者 | 进行拼接

代码演示:

1
2
3
4
5
6
复制代码    @RequestMapping("/th")
public String th(Model model){
model.addAttribute("a",1);
model.addAttribute("b",2);
return "/course/th";
}

模版页面:

1
复制代码<p th:text="${a}+${b}"></p>

结果页面:

1
复制代码<p>3</p>

—模版页面:

1
复制代码<p th:text="|${a} ${b}|"></p>

结果页面:

1
复制代码<p>1 2</p>

—模版页面:

1
复制代码<p th:text="${a} > ${b}"></p>

结果是:

1
复制代码<p>false</p>

—java代码:

1
2
3
4
5
复制代码    @RequestMapping("/th")
public String th(Model model){
model.addAttribute("flag",true);
return "/course/th";
}

模版页面:

1
复制代码<p th:text="!${flag}"></p>

结果页面:

1
复制代码<p>false</p>

*{…}和 ${…}表达式

正常情况下 {…} 和 ${…}是一样的,但是 {…} 一般和 th:object 进行一起使用来完成对象属性的简写。

代码演示:

1
2
3
4
5
6
复制代码    @RequestMapping("/th")
public String th(Model model){
User user = new User("ljk",18);
model.addAttribute("user",user);
return "/course/th";
}

使用 ${…}操作模版代码:

1
2
复制代码<p th:text="${user.name}"></p>
<p th:text="${user.age}"></p>

结果页面:

1
复制代码<p>ljk</p><p>18</p>

使用 *{…}操作模版代码:

1
2
复制代码<p th:text="*{user.name}"></p>
<p th:text="*{user.age}"></p>

结果页面:

1
复制代码<p>ljk</p><p>18</p>

使用 *{…}特有操作模版代码:

1
2
3
4
复制代码<div th:object="${user}" >
<p th:text="*{name}"></p>
<p th:text="*{age}"></p>
</div>

结果页面:

1
复制代码<p>ljk</p><p>18</p>
{…}表达式

用于国际化message.properties 属性读取定义message.properties 配置文件在这里插入图片描述在这里插入图片描述

定义国际化处理转换处理类

1
2
3
4
5
6
7
复制代码@Configuration
public class LocaleResolverConfig {
@Bean(name="localeResolver")
public LocaleResolver localeResolverBean() {
return new SessionLocaleResolver();
}
}

定义国际化处理的controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
复制代码
@Controller
public class ProductController {

@Autowired
private LocaleResolver localeResolver;
private ProductService productService = new ProductService();

@RequestMapping("/")
public String useT(Model model,HttpServletRequest request,HttpServletResponse response) {
//设置访问用户信息到session
request.getSession(true).setAttribute("user", new User("桌前", "明月", "CHINA", null));
localeResolver.setLocale(request,response,Locale.CHINA);
return "productList";
}
}

如果没有定义 messageenUS.properties 和 messagezhCN.properties 会默认取message.properties中的信息如果 Locale = Locale.CHINA 就取 messagezhCN.properties如果 Locale = Locale.US 就取 messageenUS.properties。

模版代码:

1
复制代码<p th:utext="#{home.welcome(${session.user.name})}">Welcome to our grocery store, Sebastian!</p>

访问controller的路径的效果:在这里插入图片描述

~{…}片段表达式

这个一般和模版布局的语法一起使用,具体使用方式请看下面模版布局的教程。

@{…}链接网址表达式

一般和 th:href、th:src进行结合使用,用于显示Web 应用中的URL链接。通过@{…}表达式Thymeleaf 可以帮助我们拼接上web应用访问的全路径,同时我们可以通过()进行参数的拼接

代码演示:

模版代码:

1
复制代码<img th:src="@{/images/gtvglogo.png}"  />

结果页面:

1
复制代码<img src="/sbe/images/gtvglogo.png">

—模版代码:

1
复制代码<a th:href="@{/product/comments(prodId=${prod.id})}" >查看</a>

结果页面:

1
复制代码<a href="/sbe/product/comments?prodId=2">查看</a>

—模版代码:

1
复制代码 <a  th:href="@{/product/comments(prodId=${prod.id},prodId2=${prod.id})}" >查看</a>

结果页面:

1
复制代码<a href="/sbe/product/comments?prodId=2&prodId2=2">查看</a>

条件判断 th:if/th:unless

th:if 当条件为true则显示。th:unless 当条件为false 则显示。

代码演示:

java代码:

1
2
3
4
5
复制代码    @RequestMapping("/thif")
public String thif(Model model){
model.addAttribute("flag",true);
return "/course/thif";
}

模版页面:

1
复制代码<p th:if="${flag}">if判断</p>

结果页面:

1
复制代码<p>if判断</p>

—模版页面:

1
复制代码<p th:unless="!${flag}">unless 判断</p>

结果页面:

1
复制代码<p>unless 判断</p>

switch

th:switch 我们可以通过switch来完成类似的条件表达式的操作。代码演示:java代码:

1
2
3
4
5
6
复制代码    @RequestMapping("/thswitch")
public String thswitch(Model model){
User user = new User("ljk",23);
model.addAttribute("user",user);
return "/course/thswitch";
}

模版页面:

1
2
3
4
复制代码<div th:switch="${user.name}">
<p th:case="'ljk'">User is ljk</p>
<p th:case="ljk1">User is ljk1</p>
</div>

结果页面:

1
复制代码<div><p> User is ljk</p></div>

for循环

th:each 遍历集合

代码演示:java代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
复制代码    @RequestMapping("/theach")
public String theach(Model model){

List<User> userList = new ArrayList<User>();
User user1 = new User("ljk",18);
User user2 = new User("ljk2",19);
User user3 = new User("ljk3",20);
User user4 = new User("lj4",21);
userList.add(user1);
userList.add(user2);
userList.add(user3);
userList.add(user4);
model.addAttribute("userList",userList);

List<String> strList = new ArrayList<String>();
strList.add("ljk");
strList.add("ljk2");
strList.add("ljk3");
strList.add("lj4");
model.addAttribute("strList",strList);

return "/course/theach";
}

模版页面:

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
复制代码     <table>
<thead>
<tr>
<th>用户名称</th>
<th>用户年龄</th>
</tr>
</thead>
<tbody>
<tr th:each="user : ${userList}" th:class="${userStat.odd}? 'odd'">
<td th:text="${user.name}">Onions</td>
<td th:text="${user.age}">2.41</td>
</tr>
</tbody>
</table>
----------------------------------------------------------------------
<table>
<thead>
<tr>
<th>用户名称</th>
</tr>
</thead>
<tbody>
<tr th:each="str : ${strList}" th:class="${strStat.odd}? 'odd'">
<td th:text="${str}">Onions</td>
</tr>
</tbody>
</table>

结果页面:在这里插入图片描述

我们可以通过便利的变量名+Stat 来获取索引 是否是第一个或最后一个等。便利的变量名+Stat称作状态变量,其属性有:

  • index:当前迭代对象的迭代索引,从0开始,这是索引属性;
  • count:当前迭代对象的迭代索引,从1开始,这个是统计属性;
  • size:迭代变量元素的总量,这是被迭代对象的大小属性;
  • current:当前迭代变量;
  • even/odd:布尔值,当前循环是否是偶数/奇数(从0开始计算);
  • first:布尔值,当前循环是否是第一个;
  • last:布尔值,当前循环是否是最后一个
    *
    for循环介绍内容参考了 CSDN博主liubin5620 Thymeleaf模板引擎常用属性之 th:each迭代循环:https://blog.csdn.net/liubin5620/article/details/80470619

th:href

用于声明在a 标签上的href属性的链接 该语法会和@{..} 表达式一起使用。

代码演示:java代码:

1
2
3
4
复制代码    @RequestMapping("/thhref")
public String thhref(Model model){
return "/course/thhref";
}

模版代码:

1
复制代码<a href="../home.html" th:href="@{/}">返回首页</a>

结果页面:

1
复制代码<a href="/sbe/">返回首页</a>

th:class

用于声明在标签上class 属性信息。

代码演示:java代码:

1
2
3
4
复制代码    @RequestMapping("/thclass")
public String thclass(Model model){
return "/course/thclass";
}

模版页面:

1
复制代码<p th:class=" 'even'? 'even' : 'odd'" th:text=" 'even'? 'even' : 'odd'"></p>

结果页面:

1
复制代码<p class="even">even</p>

th:attr

用于声明html中或自定义属性信息。

代码演示:

java代码:

1
2
3
4
复制代码@RequestMapping("/thattr")
public String thattr(Model model){
return "/course/thattr";
}

模版页面:

1
复制代码<img  th:attr="src=@{/images/gtvglogo.png}" />

结果页面:

1
复制代码<img src="/sbe/images/gtvglogo.png">

th:value

用于声明html中value属性信息。

代码演示:java代码:

1
2
3
4
5
复制代码@RequestMapping("/thvalue")
public String thvalue(Model model){
model.addAttribute("name", "ljk");
return "/course/thvalue";
}

模版页面:

1
复制代码    <input type="text" th:value="${name}" />

结果页面:

1
复制代码<input type="text" value="ljk">

th:action

用于声明html from标签中action属性信息。

代码演示:java代码:

1
2
3
4
复制代码@RequestMapping("/thaction")
public String thaction(Model model){
return "/course/thaction";
}

模版页面:

1
2
3
复制代码    <form action="subscribe.html" th:action="@{/subscribe}">
<input type="text" name="name" value="abc"/>
</form>

结果页面:

1
2
3
复制代码<form action="/sbe/subscribe">
<input type="text" name="name" value="abc">
</form>

th:id

用于声明htm id属性信息。

代码演示:java代码:

1
2
3
4
5
复制代码    @RequestMapping("/thid")
public String thid(Model model){
model.addAttribute("id", 123);
return "/course/thid";
}

模版页面:

1
复制代码<p th:id="${id}"></p>

结果页面:

1
复制代码<p id="123"></p>

th:inline

JavaScript内联 操作使用的语法,具体请参考下面内联操作相关介绍

th:onclick

用于声明htm 中的onclick事件。

代码演示:java代码:

1
2
3
4
复制代码@RequestMapping("/thonclick")
public String honclick(Model model){
return "/course/thonclick";
}

模版页面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
复制代码<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript">
function showUserInfo(){
alert("i am zhuoqianmingyue!")
}
</script>
</head>
<body>
<p th:onclick="'showUserInfo()'">点我</p>
</body>
</html>

结果页面:

1
复制代码<p onclick="showUserInfo()">点我</p>

th:selected

用于声明htm 中的selected属性信息。

代码演示:java代码:

1
2
3
4
5
复制代码    @RequestMapping("/thselected")
public String thselected(Model model){
model.addAttribute("sex", 1);
return "/course/thselected";
}

模版页面:

1
2
3
4
5
复制代码<select>
<option name="sex"></option>
<option th:selected="1 == ${sex}">男</option>
<option th:selected="0 == ${sex}">女</option>
</select>

结果页面:

1
2
3
4
5
复制代码<select>
<option name="sex"></option>
<option selected="selected">男</option>
<option>女</option>
</select>

th:src

用于声明htm 中的img中src属性信息。

代码演示:java代码:

1
2
3
4
复制代码@RequestMapping("/thsrc")
public String thsrc(Model model){
return "/course/thsrc";
}

模版页面:

1
复制代码<img  title="GTVG logo" th:src="@{/images/gtvglogo.png}" />

结果页面:

1
复制代码<img title="GTVG logo" src="/sbe/images/gtvglogo.png">

th:style

用于声明htm中的标签 css的样式信息。

代码演示:java代码:

1
2
3
4
5
复制代码RequestMapping("/thstyle")
public String thstyle(Model model){
model.addAttribute("isShow", true);
return "/course/thstyle";
}

模版页面:

1
复制代码<p th:style="'display:' + @{(${isShow} ? 'none' : 'block')} + ''"></p>

结果页面:

1
复制代码<p style="display:none"></p>

th:with

用于thymeleaf 模版页面中局部变量定义的使用。

代码演示:java代码:

1
2
3
4
5
复制代码    @RequestMapping("/thwith")
public String thwith(Model model){
model.addAttribute("today", new Date());
return "/course/thwith";
}

模版页面:

1
2
3
复制代码<p th:with="df='dd/MMM/yyyy HH:mm'">
Today is: <span th:text="${#dates.format(today,df)}">13 February 2011</span>
</p>

结果页面:

1
复制代码<span>02/六月/2019 06:52</span>

—java代码:

1
2
3
4
5
6
7
8
复制代码    @RequestMapping("/thwith")
public String thwith(Model model){
List<User> users = new ArrayList<User>();
users.add(new User("ljk",18));
users.add(new User("ljk2",18));
model.addAttribute("users",users);
return "/course/thwith";
}

模版页面:

1
2
3
4
5
复制代码<div th:with="firstEle=${users[0]}">
<p>
第一个用户的名称是: <span th:text="${firstEle.name}"></span>.
</p>
</div>

结果页面:

1
2
3
4
5
复制代码<div>
<p>
第一个用户的名称是: <span>ljk</span>.
</p>
</div>

还有一种用法是在模版布局中带参数的引用片段中使用方式如下:

1
复制代码<div th:replace="::frag" th:with="onevar=${value1},twovar=${value2}">

具体演示请参考模版布局中的介绍。

Elvis运算符

Elvis运算可以理解成简单的判断是否为null的三元运算的简写,如果值为nullzhe显示默认值,如果不为null 则显示原有的值。

代码演示:java代码:

1
2
3
4
5
复制代码    @RequestMapping("/elvis")
public String elvis(Model model){
model.addAttribute("age", null);
return "/course/elvis";
}

模版页面:

1
复制代码 <p>Age: <span th:text="${age}?: '年龄为nll'"></span></p>

结果页面:

1
复制代码<p>Age: <span>年龄为nll</span></p>

—java代码:

1
2
3
4
5
复制代码@RequestMapping("/elvis")
public String elvis(Model model){
model.addAttribute("age2", 18);
return "/course/elvis";
}

模版页面:

1
复制代码<p>Age2: <span th:text="${age2}?: '年龄为nll'"></span></p>

结果页面:

1
复制代码<p>Age2: <span>18</span></p>

三元表达式

我们可以在thymeleaf 的语法中使用三元表达式 具体使用方法是在th:x 中通过 表达式?1选项:2选项。

代码演示:java代码:

1
2
3
4
复制代码    @RequestMapping("/threeElementOperation")
public String threeElementOperation(Model model){
return "/course/threeElementOperation";
}

模版页面:

1
复制代码<p th:class=" 'even'? 'even' : 'odd'" th:text=" 'even'? 'even' : 'odd'"></p>

结果页面:

1
复制代码<p class="even">even</p>

—java代码:

1
2
3
4
5
复制代码    @RequestMapping("/threeElementOperation")
public String threeElementOperation(Model model){
model.addAttribute("name", "ljk");
return "/course/threeElementOperation";
}

模版页面:

1
复制代码<p th:value="${name eq 'ljk' ? '帅哥':'丑男'}" th:text="${name eq 'ljk' ? '帅哥':'丑男'}"></p>

结果页面:

1
复制代码 <p value="帅哥">帅哥</p>

条件表达式操作字符:

gt:great than(大于)

ge:great equal(大于等于)

eq:equal(等于)

lt:less than(小于)

le:less equal(小于等于)

ne:not equal(不等于)

No-Operation(_)什么都不做

Elvis运算符 的一种特殊简写操作,当显示的值为null 是就什么都不做。

代码演示:java代码:

1
2
3
4
5
复制代码@RequestMapping("/noOperation")
public String noOperation(Model model){
model.addAttribute("name", null);
return "/course/noOperation";
}

模版页面:

1
复制代码<span th:text="${name} ?: _">no user authenticated</span>

结果页面:

1
复制代码<span>no user authenticated</span>

标准方言中存在以下固定值布尔属性:

th:async th:autofocus th:autoplay
th:checked th:controls th:declare
th:default th:defer th:disabled
th:formnovalidate th:hidden th:ismap
th:loop th:multiple th:novalidate
th:nowrap th:open th:pubdate
th:readonly th:required th:reversed
th:scoped th:seamless th:selected

针对特定的HTML5属性:

th:abbr th:accept th:accept-charset
th:accesskey th:action th:align
th:alt th:archive th:audio
th:autocomplete th:axis th:background
th:bgcolor th:border th:cellpadding
th:cellspacing th:challenge th:charset
th:cite th:class th:classid
th:codebase th:codetype th:cols
th:colspan th:compact th:content
th:contenteditable th:contextmenu th:data
th:datetime th:dir th:draggable
th:dropzone th:enctype th:for
th:form th:formaction th:formenctype
th:formmethod th:formtarget th:fragment
th:frame th:frameborder th:headers
th:height th:high th:href
th:hreflang th:hspace th:http-equiv
th:icon th:id th:inline
th:keytype th:kind th:label
th:lang th:list th:longdesc
th:low th:manifest th:marginheight
th:marginwidth th:max th:maxlength
th:media th:method th:min
th:name th:onabort th:onafterprint
th:onbeforeprint th:onbeforeunload th:onblur
th:oncanplay th:oncanplaythrough th:onchange
th:onclick th:oncontextmenu th:ondblclick
th:ondrag th:ondragend th:ondragenter
th:ondragleave th:ondragover th:ondragstart
th:ondrop th:ondurationchange th:onemptied
th:onended th:onerror th:onfocus
th:onformchange th:onforminput th:onhashchange
th:oninput th:oninvalid th:onkeydown
th:onkeypress th:onkeyup th:onload
th:onloadeddata th:onloadedmetadata th:onloadstart
th:onmessage th:onmousedown th:onmousemove
th:onmouseout th:onmouseover th:onmouseup
th:onmousewheel th:onoffline th:ononline
th:onpause th:onplay th:onplaying
th:onpopstate th:onprogress th:onratechange
th:onreadystatechange th:onredo th:onreset
th:onresize th:onscroll th:onseeked
th:onseeking th:onselect th:onshow
th:onstalled th:onstorage th:onsubmit
th:onsuspend th:ontimeupdate th:onundo
th:onunload th:onvolumechange th:onwaiting
th:optimum th:pattern th:placeholder
th:poster th:preload th:radiogroup
th:rel th:rev th:rows
th:rowspan th:rules th:sandbox
th:scheme th:scope th:scrolling
th:size th:sizes th:span
th:spellcheck th:src th:srclang
th:standby th:start th:step
th:style th:summary th:tabindex
th:target th:title th:type
th:usemap th:value th:valuetype
th:vspace th:width th:wrap
th:xmlbase th:xmllang th:xmlspace

内联

如何使用内连操作

我们可以通过 在父标签声明 th:inline=”text” 来开启内联操作。当然如果想整个页面使用可以直接声明在body上即可。具体使用方式如下面代码所示。

模版页面:

1
2
3
复制代码<div th:inline="text">
<p>Hello, [[${user.name}]]!</p>
</div>

结果内容如下:

1
2
3
复制代码<div>
<p>Hello,zhuoqianmingyue!</p>
</div>

这样的操作和使用th:text是等同的。

1
2
3
复制代码<div>
<p th:text="Hello,+${user.name}"></p>
</div>

[[…]]对应于th:text,[(…)]对应于th:utext

禁用内联操作

这我们可以通过在父标签或者本标签上声明th:inline=”none”来禁用内联的操作,如下面代码所示:模版页面:

1
复制代码<p th:inline="none">A double array looks like this: [[1, 2, 3], [4, 5]]!</p>

结果页面:

1
复制代码<p>A double array looks like this: [[1, 2, 3], [4, 5]]!</p>

JavaScript内联

如果我们想在JavaScript 中使用内联操作,需要在 script 标签上声明 th:inline=”javascript” 然后我们就可以 script 标签中使用内联操作了。具体使用方式如下面代码所示:模版页面:

1
2
3
复制代码<script th:inline="javascript">
var username = [[${user.name}]];
</script>

结果页面:

1
2
3
复制代码<script th:inline="javascript">
var username = "zhuoqianmingyue";
</script>

CSS内联

我们可以通过在 style 标签上声明 th:inline=”css” 来开启在css中使用内联的操作,具体操作方式如下:

1
2
3
复制代码<style th:inline="css">
...
</style>

例如,假设我们将两个变量设置为两个不同的String值:classname = ‘main elems’align = ‘center’我们可以像以下一样使用它们:

1
2
3
4
5
复制代码<style th:inline="css">
.[[${classname}]] {
text-align: [[${align}]];
}
</style>

结果页面:

1
2
3
4
5
复制代码<style th:inline="css">
.main\ elems {
text-align: center;
}
</style>

模板布局

定义引用片段代码

SpringBoot2.0 使用模版模版布局需要先引入 thymeleaf的 thymeleaf-layout-dialect依赖

1
2
3
4
复制代码<dependency>
<groupId>nz.net.ultraq.thymeleaf</groupId>
<artifactId>thymeleaf-layout-dialect</artifactId>
</dependency>

定义footer.html页面 该页面就是我们的引用片段代码

1
2
3
4
5
6
7
8
9
10
11
12
复制代码<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</div>
</body>
</html>

我们可以通过 th:fragment 来定义引用片段,然后可以在其他页面进行引用。

定义引用页面 index.html

1
2
3
4
5
6
7
8
9
10
11
复制代码<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>

<div th:insert="~{footer :: copy}"></div>
</body>
</html>

通过 th:insert 和 ~{…}片段引用表达式 进行引入footer.html中定义的片段

定义访问index页面的 controller

1
2
3
4
5
6
7
8
复制代码@Controller
@RequestMapping("/layout")
public class LayOutController {
@RequestMapping("/index")
public String index(){
return "/layout/index";
}
}

进行测试http://localhost:8090/sbe/layout/index在这里插入图片描述结果页面:

1
2
3
4
5
复制代码<div>
<div>
© 2011 The Good Thymes Virtual Grocery
</div>
</div>

如下面的代码2种方式的写法是一致的。如果你觉得~{footer :: copy}写法比较麻烦可以采用简写的方式footer :: copy。

1
2
复制代码<div th:insert="footer :: copy"></div>
<div th:insert="~{footer :: copy}"></div>

通过id属性来声明片段

我们可以通过 th:fragment 来定义引用片段,但是我们也可以通过在引用片段代码上声明id属性的方式进行片段的引用,具体操作方式如下:

定义引用片段代码模版页面 footer.html

1
2
3
4
5
6
7
8
9
10
11
12
复制代码<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="copy-section" >
© 2011 The Good Thymes Virtual Grocery
</div>
</body>
</html>

引用引用片段的模版页面:index.html

1
2
3
4
5
6
7
8
9
10
复制代码<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div th:insert="~{footer :: #copy-section}"></div>
</body>
</html>

结果页面:

1
2
3
4
5
复制代码<div>
<div id="copy-section">
© 2011 The Good Thymes Virtual Grocery
</div>
</div>

footer :: #copy-section和~{footer :: #copy-section} 结果是一致的。

th:insert和th:replace(和th:include)之间的区别

  • th:insert 是最简单的:他会将使用th:insert的标签 和引用片段的内容都显示出来
  • th:replace 插入引用片段的标签和内容
  • th:include类似于th:insert,只插入此片段的内容。

th:insertjava代码:

1
2
3
4
5
6
7
8
复制代码@Controller
@RequestMapping("/layout")
public class LayoutController {
@RequestMapping("/index2")
public String index2(Model model) {
return "/layout/index2";
}
}

声明引用片段模版页面:footer2.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 here</title>
</head>
<body>
<footer th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</footer>
</body>
</html>

引用片段模版页面:index2.html

1
2
3
4
5
6
7
8
9
10
11
12
复制代码<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<div th:insert="footer2 :: copy"></div>
<div th:replace="footer2 :: copy"></div>
<div th:include="footer2:: copy"></div>
</body>
</html>

—th:insert 结果:

1
2
3
4
5
复制代码<div>
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
</div>

—th:replace结果:

1
2
3
复制代码<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>

—th:include结果:

1
2
3
复制代码<div>
© 2011 The Good Thymes Virtual Grocery
</div>

带参数的引用片段

定义引用片段代码模版页面 footer.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 here</title>
</head>
<body>
<div th:fragment="frag (onevar,twovar)">
<p th:text="${onevar} + ' - ' + ${twovar}">...</p>
</div>
</body>
</html>

引用引用片段的模版页面:index.html

1
2
3
4
5
6
7
8
9
10
复制代码<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<div th:insert="footer :: frag('a','b')"></div>
</body>
</html>

结果页面:

1
2
3
4
5
复制代码<div>
<div>
<p>a - b</p>
</div>
</div>

th:insert=”footer ::frag (onevar=’a’,twovar=’b’)” 和th:insert=”footer :: frag(‘a’,’b’)效果是相等的。还有另一种写法就是使用th:withth:insert=”::frag” th:with=”onevar=’a’,twovar=’b’”

删除模版片段

我们为了方便通过直接查看下面的页面 productList.html (主要是为了作为原型页面进行查看)我们需要添加一些模拟数据。

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
复制代码<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
<th>IN STOCK</th>
<th>COMMENTS</th>
</tr>
<tr th:each="prod : ${prods}" th:class="${prodStat.odd}? 'odd'">
<td th:text="${prod.name}">Onions</td>
<td th:text="${prod.price}">2.41</td>
<td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
<td>
<span th:text="${#lists.size(prod.comments)}">2</span> comment/s
<a href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"
th:unless="${#lists.isEmpty(prod.comments)}">view</a>
</td>
</tr>
<tr class="odd">
<td>Blue Lettuce</td>
<td>9.55</td>
<td>no</td>
<td>
<span>0</span> comment/s
</td>
</tr>
<tr>
<td>Mild Cinnamon</td>
<td>1.99</td>
<td>yes</td>
<td>
<span>3</span> comment/s
<a href="comments.html">view</a>
</td>
</tr>
</table>

在上面的代码中模拟数据的代码,但是我们通过正常的controller访问该页面的时候会显示出下面的模拟数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
复制代码 <tr class="odd">
<td>Blue Lettuce</td>
<td>9.55</td>
<td>no</td>
<td>
<span>0</span> comment/s
</td>
</tr>
<tr>
<td>Mild Cinnamon</td>
<td>1.99</td>
<td>yes</td>
<td>
<span>3</span> comment/s
<a href="comments.html">view</a>
</td>
</tr>

我们直接查看该页面的效果如下:在这里插入图片描述

通过url访问查看该页面的效果:在这里插入图片描述

thymeleaf 为我们提供了 th:remove 帮助我们解决这个问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
复制代码 <tr class="odd" th:remove="all">
<td>Blue Lettuce</td>
<td>9.55</td>
<td>no</td>
<td>
<span>0</span> comment/s
</td>
</tr>
<tr th:remove="all">
<td>Mild Cinnamon</td>
<td>1.99</td>
<td>yes</td>
<td>
<span>3</span> comment/s
<a href="comments.html">view</a>
</td>
</tr>

我们在模拟数据上声明th:remove=”all” 后在此通过url访问 没有了我们之前的模拟数据在这里插入图片描述

直接查看该页面还是可以查看到我们的模拟数据的。在这里插入图片描述

all属性中的这个值是什么意思?th:remove可以根据其价值以五种不同的方式表现:

  • all:删除包含标记及其所有子标记。
  • body:不要删除包含标记,但删除其所有子标记。
  • tag:删除包含标记,但不删除其子项。
  • all-but-first:删除除第一个之外的所有包含标记的子项。
  • none: 没做什么。此值对于动态评估很有用。

当我们知道没有属性的含义后我们可以通过在 声明一次即可,无需在通过定义多个 th:remove=”all”

预定义的工具对象

dates

处理日期数据 生成,转换,获取日期的具体天数 年数。

代码演示:

java代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
复制代码    @RequestMapping("/dates")
public String dates(Model model) throws ParseException{
Date date = new Date();
model.addAttribute("date",date);

String dateStr = "2018-05-30";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date2 = sdf.parse(dateStr);
Date[] datesArray = new Date[2];
datesArray[0] = date;
datesArray[1] = date2;
model.addAttribute("datesArray",datesArray);

List<Date> datesList = new ArrayList<Date>();
datesList.add(date);
datesList.add(date2);
model.addAttribute("datesList",datesList);
return "/course/dates";
}

format操作

java代码:

1
复制代码Date date = new Date();

模版页面:

1
复制代码<span th:text="${#dates.format(date)}">4564546</span>

结果页面:

1
复制代码<span>2019年5月30日 上午10时03分24秒 </span>

—java代码:

1
复制代码Date date = new Date();

模版页面:

1
复制代码<span th:text="${#dates.format(date, 'dd/MMM/yyyy HH:mm')}">4564546</span>

结果页面:

1
复制代码<span>30/五月/2019 10:03 </span>

—java代码:

1
2
3
复制代码Date[] datesArray = new Date[2];
datesArray[0] = date;
datesArray[1] = date2;

模版页面:

1
复制代码<p th:text="${#dates.format(datesArray, 'yyyy-MM-dd HH:mm')}"></p>

结果页面:

1
复制代码<p>2019-05-30 10:03</p>

不知为何这里只是取出了一个日期数据

—java代码:

1
2
3
4
复制代码List<Date> datesList = new ArrayList<Date>();
datesList.add(date);
datesList.add(date2);
model.addAttribute("datesList",datesList);

模版页面:

1
复制代码<p th:text="${#dates.listFormat(datesList, 'dd/MMM/yyyy HH:mm')}"></p>

结果页面:

1
复制代码<p>[30/五月/2019 10:03, 30/五月/2018 00:00]</p>

获取日期属性操作java代码:

1
复制代码Date date = new Date();

模版页面:

1
复制代码<p th:text="${#dates.day(date)} "></p>

结果页面:

1
复制代码<p>30</p>

—java代码:

1
复制代码Date date = new Date();

模版页面:

1
复制代码<p th:text="${#dates.month(date)}"></p>

结果页面:

1
复制代码<p>5</p>

—java代码:

1
复制代码Date date = new Date();

模版页面:

1
复制代码<p th:text="${#dates.monthName(date)}"></p>

结果页面:

1
复制代码<p>五月</p>

—java代码:

1
复制代码Date date = new Date();

模版页面:

1
复制代码<p th:text="${#dates.monthNameShort(date)} "></p>

结果页面:

1
复制代码<p>五月</p>

—java代码:

1
复制代码Date date = new Date();

模版页面:

1
复制代码<p th:text="${#dates.year(date)}"></p>

结果页面:

1
复制代码<p>2019</p>

—java代码:

1
复制代码Date date = new Date();

模版页面:

1
复制代码<p th:text="${#dates.dayOfWeek(date)}"></p>

结果页面:

1
复制代码<p>5</p>

—java代码:

1
复制代码Date date = new Date();

模版页面:

1
复制代码<p th:text="${#dates.dayOfWeekName(date)}"></p>

结果页面:

1
复制代码<p>星期四</p>

—java代码:

1
复制代码Date date = new Date();

模版页面:

1
复制代码<p th:text="${#dates.dayOfWeekNameShort(date)}"></p>

结果页面:

1
复制代码<p>星期四</p>

—java代码:

1
复制代码Date date = new Date();

模版页面:

1
复制代码<p th:text="${#dates.hour(date)}"></p>

结果页面:

1
复制代码<p>10</p>

—java代码:

1
复制代码Date date = new Date();

模版页面:

1
复制代码<p th:text="${#dates.minute(date)}"></p>

结果页面:

1
复制代码<p>10</p>

—java代码:

1
复制代码Date date = new Date();

模版页面:

1
复制代码<p th:text="${#dates.second(date)}"></p>

结果页面:

1
复制代码<p>45</p>

—java代码:

1
复制代码Date date = new Date();

模版页面:

1
复制代码<p th:text="${#dates.millisecond(date)} "></p>

结果页面:

1
复制代码<p>853</p>

生成日期操作

模版页面:

1
复制代码<p th:text="${#dates.createNow()}"></p>

结果页面:

1
复制代码<p>Thu May 30 10:15:55 CST 2019</p>

—模版页面:

1
复制代码<p th:text="${#dates.format(#dates.createNow())}"></p>

结果页面:

1
复制代码<p>2019年5月30日 上午10时15分55秒</p>

—模版页面:

1
复制代码<p th:text="${#dates.create('2019','05','30')}"></p>

结果页面:

1
复制代码<p>Thu May 30 00:00:00 CST 2019</p>

—模版页面:

1
复制代码<p th:text="${#dates.create('2019','05','31','10','18')}"></p>

结果页面:

1
复制代码<p>Fri May 31 10:18:00 CST 2019</p>

—模版页面:

1
复制代码<p th:text="${#dates.create('2019','05','30','10','18','34')}"></p>

结果页面:

1
复制代码<p>Thu May 30 10:18:34 CST 2019</p>

—模版页面:

1
复制代码<p th:text="${#dates.createToday()}"></p>

结果页面:

1
复制代码<p>Thu May 30 00:00:00 CST 2019</p>
numbers

处理数字数据的转换。包括:

  • 对不够位数的数字进行补0(formatInteger )
  • 设置千位分隔符(formatInteger)
  • 精确小数点(formatDecimal )
  • 设置百分号(formatPercent )
  • 生成数组(sequence )

代码演示:

1
2
3
4
复制代码    @RequestMapping("/numbers")
public String numbers(Model model) throws ParseException{
return "/course/numbers";
}

数字进行补0操作

模板代码:

1
复制代码<p th:text="${#numbers.formatInteger('123',4)}"></p>

结果页面:

1
复制代码<p>0123</p>

—模板代码:

1
复制代码<p th:text="${#numbers.formatInteger('123',3)}"></p>

结果页面:

1
复制代码<p>123</p>

—模板代码:

1
复制代码<p th:text="${#numbers.formatInteger('123',2)}"></p>

结果页面:

1
复制代码<p>123</p>

—Java代码

1
2
3
4
5
6
7
8
9
复制代码    @RequestMapping("/numbers")
public String numbers(Model model) throws ParseException{
List<Integer> numList = new ArrayList<Integer>();
numList.add(1);
numList.add(12);
numList.add(13);
model.addAttribute("numList",numList);
return "/course/numbers";
}

模板代码:

1
复制代码<p th:text="${#numbers.listFormatInteger(numList,3)}"></p>

结果页面:

1
复制代码<p>[001, 012, 013]</p>

千位分隔符操作模板代码:

1
复制代码<p th:text="${#numbers.formatInteger('1000',2,'POINT')}"></p>

结果页面:

1
复制代码<p>1.000</p>

—模板代码:

1
复制代码<p th:text="${#numbers.formatInteger('1000',6,'POINT')}"></p>

结果页面:

1
复制代码<p>001.000</p>

—模板代码:

1
复制代码<p th:text="${#numbers.formatInteger('1000',7,'POINT')}"></p>

结果页面:

1
复制代码<p>0.001.000</p>

—模板代码:

1
复制代码<p th:text="${#numbers.formatInteger('1000',2,'COMMA')}"></p>

结果页面:

1
复制代码<p>1,000</p>

—模板代码:

1
复制代码<p th:text="${#numbers.formatInteger('1000',2,'WHITESPACE')}"></p>

结果页面:

1
复制代码<p>1 000</p>

—模板代码:

1
复制代码<p th:text="${#numbers.formatInteger('1000',2,'NONE')}"></p>

结果页面:

1
复制代码<p>1000</p>

—模板代码:

1
复制代码<p th:text="${#numbers.formatInteger('1000',2,'DEFAULT')}"></p>

结果页面:

1
复制代码<p>1,000</p>

精确小数点操作模板代码:

1
复制代码<p th:text="${#numbers.formatDecimal('10.123',3,2)}"></p>

结果页面:

1
复制代码<p>010.12</p>

—模板代码:

1
复制代码<p th:text="${#numbers.formatDecimal('1000.123',5,'POINT',2,'COMMA')}"></p>

结果页面:

1
复制代码<p>01.000,12</p>

钱显示符号操作

模板代码:

1
复制代码<p th:text="${#numbers.formatCurrency('1000')}"></p>

结果页面:

1
复制代码<p>¥1,000.00</p>

百分比操作模板代码:

1
复制代码<p th:text="${#numbers.formatPercent('0.2',2, 4)}"></p>

结果页面:

1
复制代码<p>20.0000%</p>

—模板代码:

1
复制代码<p th:text="${#numbers.formatPercent('0.2',3, 2)}"></p>

结果页面:

1
复制代码<p>020.00%</p>

生成数组操作

模板代码:

1
2
3
复制代码<div th:each="num : ${#numbers.sequence(0,4)}" >
<p th:text="${num}"></p>
</div>

结果页面:

1
2
3
4
5
复制代码<div><p>0</p></div>
<div><p>1</p></div>
<div><p>2</p></div>
<div><p>3</p></div>
<div><p>4</p></div>

—模板代码:

1
2
3
复制代码<div th:each="num : ${#numbers.sequence(0,4,1)}" >
<p th:text="${num}"></p>
</div>

结果页面:

1
2
3
4
5
复制代码<div><p>0</p></div>
<div><p>1</p></div>
<div><p>2</p></div>
<div><p>3</p></div>
<div><p>4</p></div>

—模板代码:

1
2
3
复制代码<div th:each="num : ${#numbers.sequence(0,10,2)}" >
<p th:text="${num}"></p>
</div>

结果页面:

1
2
3
复制代码<div><p>0</p></div>
<div><p>2</p></div>
<div><p>4</p></div>
strings

处理String的相关操作,包括:

  • 字符串转换(toString)
  • 检查字符串是否为空(isEmpty)
  • 字符串是为空替换操作(defaultString)
  • 检查字符串中是否包含某个字符串(contains containsIgnoreCase)
  • 检查字符串是以片段开头还是结尾(startsWith endsWith)
  • 截取(substring substringAfter)
  • 替换(replace)
  • 追加(prepend append)
  • 变更大小写(toUpperCase toLowerCase)
  • 拆分和组合字符串(arrayJoin arraySplit)
  • 去空格(trim)
  • 缩写文本(abbreviate)
  • 字符串连接(concat)

代码演示:java 代码

1
2
3
4
5
6
7
8
9
10
11
复制代码@RequestMapping("/strings")
public String strings(Model model){
Object object = "123";
model.addAttribute("object",object);

List<Integer> numList = new ArrayList<Integer>();
numList.add(1);
numList.add(12);
numList.add(13);
model.addAttribute("numList",numList);
}

Java代码

1
复制代码Object object = "123";

模板代码:

1
复制代码<p th:text="${object}"></p>

结果页面:

1
复制代码<p>123</p>

toString操作

Java代码

1
复制代码Object object = "123";

模板代码:

1
复制代码<p th:text="${#strings.toString(object)}"></p>

结果页面:

1
复制代码<p>123</p>

—Java代码

1
2
3
4
复制代码List<Integer> numList = new ArrayList<Integer>();
numList.add(1);
numList.add(12);
numList.add(13);

模板代码:

1
复制代码<p th:text="${#strings.toString(numList)}"></p>

结果页面:

1
复制代码<p>[1, 12, 13]</p>

isEmpty操作Java代码

1
复制代码String name = null;

模板代码:

1
复制代码<p th:text="${#strings.isEmpty(name)}"></p>

结果页面:

1
复制代码<p>true</p>

—Java代码

1
2
3
复制代码List<String> nameList = new ArrayList<String>();
nameList.add("1");
nameList.add(null);

模板代码:

1
复制代码<p th:text="${#strings.listIsEmpty(nameList)}"></p>

结果页面:

1
复制代码<p>[false, true]</p>

—Java代码

1
2
3
复制代码Set<String> nameSet = new HashSet<String>();
nameSet.add(null);
nameSet.add("1");

模板代码:

1
复制代码<p th:text="${#strings.setIsEmpty(nameSet)}"></p>

结果页面:

1
复制代码<p>[true, false]</p>

defaultString操作Java代码

1
复制代码String name = null;

模板代码:

1
复制代码<p th:text="${#strings.defaultString(text,'该值为null')}"></p>

结果页面:

1
复制代码<p>该值为null</p>

—Java代码

1
2
3
复制代码List<String> nameList = new ArrayList<String>();
nameList.add("1");
nameList.add(null);

模板代码:

1
复制代码<p th:text="${#strings.listDefaultString(textList,'该值为null')}"></p>

结果页面:

1
复制代码<p>[abc, 该值为null]</p>

contains操作模板代码:

1
复制代码<p th:text="${#strings.contains('abcez','ez')}"></p>

结果页面:

1
复制代码<p>true</p>

—模板代码:

1
复制代码<p th:text="${#strings.containsIgnoreCase('abcEZ','ez')}"></p>

结果页面:

1
复制代码<p>true</p>

startsWith endsWith 操作

模板代码:

1
复制代码<p th:text="${#strings.startsWith('Donabcez','Don')}"></p>

结果页面:

1
复制代码<p>true</p>

—模板代码:

1
复制代码<p th:text="${#strings.endsWith('Donabcezn','n')}"></p>

结果页面:

1
复制代码<p>true</p>

indexOf操作模板代码:

1
复制代码<p th:text="${#strings.indexOf('abcefg','e')}"></p>

结果页面:

1
复制代码<p>3</p>

substring操作模板代码:

1
复制代码<p th:text="${#strings.substring('abcefg',3,5)}"></p>

结果页面:

1
复制代码<p>ef</p>

replace操作模板代码:

1
复制代码<p th:text="${#strings.replace('lasabce','las','ler')}"></p>

结果页面:

1
复制代码<p>lerabce</p>

prepend操作模板代码:

1
复制代码<p th:text="${#strings.prepend('abc','012')}"></p>

结果页面:

1
复制代码<p>012abc</p>

append操作模板代码:

1
复制代码<p th:text="${#strings.append('abc','456')}"></p>

结果页面:

1
复制代码<p>abc456</p>

toUpperCase操作模板代码:

1
复制代码<p th:text="${#strings.toUpperCase('abc')}"></p>

结果页面:

1
复制代码<p>ABC</p>

toLowerCase操作模板代码:

1
复制代码<p th:text="${#strings.toLowerCase('ABC')}"></p>

结果页面:

1
复制代码<p>abc</p>

length操作模板代码:

1
复制代码<p th:text="${#strings.length('abc')}"></p>

结果页面:

1
复制代码<p>3</p>

trim操作模板代码:

1
复制代码<p th:text="${#strings.trim(' abc ')}"></p>

结果页面:

1
复制代码<p>abc</p>

abbreviate操作模板代码:

1
复制代码<p th:text="${#strings.abbreviate('12345678910',10)}"></p>

结果页面:

1
复制代码<p>1234567...</p>

#objects

处理Object对象的操作 包含obj不为空返回改值如果为空返回默认值(nullSafe)java代码

1
2
3
4
5
复制代码@RequestMapping("/objects")
public String objects(Model model){
Object obj = null;
model.addAttribute("obj",obj);
}

模板代码:

1
复制代码<p th:text="${#objects.nullSafe(obj,'该对象为null')}"></p>

结果页面:

1
复制代码<p>该对象为null</p>
bools

判断对象是否为ture或者是否为false的操作。

  • 数字 1 为 ture , 0 为 false;
  • “on” 为 true, “off” 为false;
  • “true” 为true, “false”为 false;

isTrue操作模板代码:

1
复制代码<p th:text="${#bools.isTrue(true)} "></p>

结果页面:

1
复制代码<p>true</p>

—模板代码:

1
复制代码<p th:text="${#bools.isTrue(false)} "></p>

结果页面:

1
复制代码<p>false</p>

—模板代码:

1
复制代码<p th:text="${#bools.isTrue('on')} "></p>

结果页面:

1
复制代码<p>true</p>

—模板代码:

1
复制代码<p th:text="${#bools.isTrue('off')} "></p>

结果页面:

1
复制代码<p>false</p>

—模板代码:

1
复制代码<p th:text="${#bools.isTrue('true')} "></p>

结果页面:

1
复制代码<p>true</p>

—模板代码:

1
复制代码<p th:text="${#bools.isTrue('false')} "></p>

结果页面:

1
复制代码<p>false</p>

模板代码:

1
复制代码<p th:text="${#bools.isTrue(1)} "></p>

结果页面:

1
复制代码<p>true</p>

—模板代码:

1
复制代码<p th:text="${#bools.isTrue(0)} "></p>

结果页面:

1
复制代码<p>false</p>
arrays

处理数组的相关操作的内置对象,包含:

  • 转换数组 toStringArray toIntegerArray,
  • 获取数组的长度(length ),
  • 判断数组是否为空(isEmpty )
  • 是否包含某个元素(contains)
  • 是否包含一批元素(containsAll)

其中 toStringArray 等操作接受的是Object对象,containsAll 接受一批元素支持数组和集合的参数。

toStringArray操作java代码

1
2
3
4
5
6
7
复制代码@RequestMapping("/arrays")
public String arrays(Model model){
List<String> object = new ArrayList<String>();
object.add("1");
object.add("2");
model.addAttribute("object",object);
}

模板代码:

1
复制代码 <p th:text="${#arrays.toStringArray(object)} "></p>

结果页面:

1
复制代码<p>[Ljava.lang.String;@3cca655d</p>

length操作java代码

1
复制代码Integer[] array = {1,2,3};

模板代码:

1
复制代码 <p th:text="${#arrays.length(array)} "></p>

结果页面:

1
复制代码<p>3</p>

isEmpty操作java代码

1
复制代码Integer[] array = {1,2,3};

模板代码:

1
复制代码 <p th:text="${#arrays.isEmpty(array)} "></p>

结果页面:

1
复制代码<p>false</p>

contains操作java代码

1
复制代码Integer[] array = {1,2,3};

模板代码:

1
复制代码<p th:text="${#arrays.contains(array,1)} "></p>

结果页面:

1
复制代码<p>true</p>

containsAll操作java代码

1
2
复制代码Integer[] array = {1,2,3};
Integer[] array2 = {1,3};

模板代码:

1
复制代码 <p th:text="${#arrays.containsAll(array,array2)} "></p>

结果页面:

1
复制代码<p>true</p>
lists

处理 list 相关操作的内置对象,包括:

  • 计算长度(size)
  • 检查list是否为空(isEmpty)
  • 检查元素是否包含在list中(contains,containsAll)
  • 对给定list的副本排序(sort)

java代码

1
2
3
4
5
6
7
8
复制代码@RequestMapping("/lists")
public String lists(Model model){
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(3);
list.add(2);
model.addAttribute("list",list);
}

模板代码:

1
复制代码<p th:text="${#lists.size(list)} "></p>

结果页面:

1
复制代码<p>3</p>

—java代码:

1
2
3
4
复制代码 List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(3);
list.add(2);

模板代码:

1
复制代码<p th:text="${#lists.isEmpty(list)} "></p>

结果页面:

1
复制代码<p>false</p>

—java代码:

1
2
3
4
复制代码 List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(3);
list.add(2);

模板代码:

1
复制代码<p th:text="${#lists.contains(list, 1)}"></p>

结果页面:

1
复制代码<p>true</p>

—java代码:

1
2
3
4
5
6
7
复制代码List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(3);
list.add(2);
List<Integer> list2 = new ArrayList<Integer>();
list2.add(1);
list2.add(2);

模板代码:

1
2
复制代码<!-- elements 可以是数组 集合 list -->
<p th:text="${#lists.containsAll(list,list2)}"></p>

结果页面:

1
复制代码<p>true</p>

—java代码:

1
2
3
4
复制代码List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(3);
list.add(2);

模板代码:

1
复制代码<p th:text="${#lists.sort(list)}"></p>

结果页面:

1
复制代码<p>[1, 2, 3]</p>
sets

处理 set 相关操作的内置对象,包括:

  • 转换为Set(toSet)
  • 计算长度(size)
  • 检查set是否为空(isEmpty)
  • 检查元素是否包含在set中 (contains,containsAll)

size操作

java代码

1
2
3
4
5
6
7
8
9
复制代码@RequestMapping("/sets")
public String sets(Model model){
Set<Integer> set = new HashSet<Integer>();
set.add(1);
set.add(2);
set.add(3);
set.add(4);
model.addAttribute("set",set);
}

模板代码:

1
复制代码<p th:text="${#sets.size(set)} "></p>

结果页面:

1
复制代码<p>3</p>

isEmpty 操作

java代码:

1
2
3
4
5
复制代码Set<Integer> set = new HashSet<Integer>();
set.add(1);
set.add(2);
set.add(3);
set.add(4);

模板代码:

1
复制代码<p th:text="${#sets.isEmpty(set)} "></p>

结果页面:

1
复制代码<p>false</p>

contains操作

java代码:

1
2
3
4
5
复制代码Set<Integer> set = new HashSet<Integer>();
set.add(1);
set.add(2);
set.add(3);
set.add(4);

模板代码:

1
复制代码<p th:text="${#sets.contains(set, 1)}"></p>

结果页面:

1
复制代码<p>true</p>

containsAll操作

java代码

1
2
3
4
5
6
7
8
复制代码Set<Integer> set = new HashSet<Integer>();
set.add(1);
set.add(2);
set.add(3);
set.add(4);

Integer[] elements = {1,2};
model.addAttribute("elements",elements);

模板代码:

1
复制代码<p th:text="${#sets.containsAll(set, elements)}"></p>

结果页面:

1
复制代码<p>true</p>

sort操作

java代码:

1
2
3
4
5
复制代码Set<Integer> set = new HashSet<Integer>();
set.add(1);
set.add(2);
set.add(3);
set.add(4);

模板代码:

1
复制代码<p th:text="${#lists.sort(list)}"></p>

结果页面:

1
复制代码<p>[1, 2, 3]</p>
maps

处理 map相关操作的内置对象,包括:

  • 计算长度(size)
  • 检查map是否为空(isEmpty)
  • 检查映射中是否包含键或值(containsKey,containsAllKeys,containsValue)

java代码:

1
2
3
4
5
6
7
8
复制代码@RequestMapping("/maps")
public String maps(Model model){
Map<String,Integer> map = new HashMap<String,Integer>();
map.put("1",1);
map.put("2",2);
map.put("3",3);
model.addAttribute("map",map);
}

模板代码:

1
复制代码<p th:text="${#maps.size(map)} "></p>

结果页面:

1
复制代码<p>3</p>

—java代码:

1
2
3
4
复制代码   Map<String,Integer> map = new HashMap<String,Integer>();
map.put("1",1);
map.put("2",2);
map.put("3",3);

模板代码:

1
复制代码<p th:text="${#maps.isEmpty(map)} "></p>

结果页面:

1
复制代码<p>false</p>

java代码:

1
2
3
4
复制代码   Map<String,Integer> map = new HashMap<String,Integer>();
map.put("1",1);
map.put("2",2);
map.put("3",3);

模板代码:

1
复制代码<p th:text="${#maps.containsKey(map, '1')}"></p>

结果页面:

1
复制代码<p>true</p>

java代码:

1
2
3
4
5
6
复制代码Map<String,Integer> map = new HashMap<String,Integer>();
map.put("1",1);
map.put("2",2);
map.put("3",3);
String[] keys = {"1","2"};
model.addAttribute("keys",keys);

模板代码:

1
2
复制代码<!-- keys 可以是数组可以是集合 -->
<p th:text="${#maps.containsAllKeys(map, keys)}"></p>

结果页面:

1
复制代码<p>true</p>

—java代码:

1
2
3
4
复制代码Map<String,Integer> map = new HashMap<String,Integer>();
map.put("1",1);
map.put("2",2);
map.put("3",3);

模板代码:

1
复制代码<p th:text="${#maps.containsValue(map, 2)}"></p>

结果页面:

1
复制代码<p>true</p>

java代码:

1
2
3
4
5
6
复制代码Map<String,Integer> map = new HashMap<String,Integer>();
map.put("1",1);
map.put("2",2);
map.put("3",3);
Integer[] values = {1,2};
model.addAttribute("values",values);

模板代码:

1
2
复制代码<!-- values 可以是数组可以是集合 -->
<p th:text="${#maps.containsAllValues(map, values)}"></p>

结果页面:

1
复制代码<p>true</p>
aggregates

用户处理集合或者数组的一些统计操作,包括:

  • 求和(sum)
  • 求平均值(avg)
  • 处理包装类型或基本类型的数组或集合

求和操作

java代码:

1
2
3
4
5
6
复制代码@RequestMapping("/aggregates")
public String aggregates(Model model){
Integer[] array = {1,2,3,4};
model.addAttribute("array",array);
return "/course/aggregates";
}

模板代码:

1
复制代码<p th:text="${#aggregates.sum(array)} "></p>

结果页面:

1
复制代码<p>10</p>

java代码:

1
2
3
4
5
复制代码List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);

模板代码:

1
复制代码<p th:text="${#aggregates.sum(list)} "></p>

结果页面:

1
复制代码<p>10</p>

求平均值操作

java代码:

1
复制代码 Integer[] array = {1,2,3,4};

模板代码:

1
复制代码<p th:text="${#aggregates.avg(array)} "></p>

结果页面:

1
复制代码<p>2.5</p>

java代码:

1
2
3
4
5
复制代码List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);

模板代码:

1
复制代码<p th:text="${#aggregates.avg(list)} "></p>

结果页面:

1
复制代码<p>2.5</p>

小结

本文主要介绍 Thymeleaf 的基础用法、内联、模板布局、预定义的工具对象。整体来看Thymeleaf 使用语法还是很强大的,但是我这里不会强烈安利你使用 Thymeleaf,正如 Thymeleaf 官方所说:“无论如何,比较技术的最好方法是自己使用它们,并感觉哪个最适合你!” 你同样可以选择使用 Velocity 或 FreeMarker。

代码示例

具体代码示例请查看我的GitHub 仓库 springbootexamples 中的 spring-boot-2.x-thymeleaf 下的 course 包下查看。

GitHub:https://github.com/zhuoqianmingyue/springbootexamples

参考文献:

https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html

https://blog.csdn.net/liubin5620/article/details/80470619

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…857858859…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%