Dunwu Blog

大道至简,知易行难

SpringBoot 之应用 EasyUI

EasyUI 是一个简单的用户界面组件的集合。由于 EasyUI 已经封装好大部分 UI 基本功能,能帮用户减少大量的 js 和 css 代码。所以,EasyUI 非常适合用于开发简单的系统或原型系统。

本文示例使用技术点:

  • Spring Boot:主要使用了 spring-boot-starter-web、spring-boot-starter-data-jpa
  • EasyUI:按需加载,并没有引入所有的 EasyUI 特性
  • 数据库:为了测试方便,使用 H2

img

简介

什么是 EasyUI?

  • easyui 是基于 jQuery、Angular.、Vue 和 React 的用户界面组件的集合。
  • easyui 提供了构建现代交互式 javascript 应用程序的基本功能。
  • 使用 easyui,您不需要编写许多 javascript 代码,通常通过编写一些 HTML 标记来定义用户界面。
  • 完整的 HTML5 网页框架。
  • 使用 easyui 开发你的产品时可以大量节省你的时间和规模。
  • easyui 使用非常简单但功能非常强大。

Spring Boot 整合 EasyUI

配置

application.properties 修改:

1
2
spring.mvc.view.prefix = /views/
spring.mvc.view.suffix = .html

引入 easyui

EasyUI 下载地址:http://www.jeasyui.cn/download.html

src/main/resources/static 目录下引入 easyui。

然后在 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
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<link
rel="stylesheet"
type="text/css"
href="../lib/easyui/themes/bootstrap/easyui.css"
/>
<link
rel="stylesheet"
type="text/css"
href="../lib/easyui/themes/icon.css"
/>
<link
rel="stylesheet"
type="text/css"
href="../lib/easyui/themes/color.css"
/>
<script type="text/javascript" src="../lib/easyui/jquery.min.js"></script>
<script
type="text/javascript"
src="../lib/easyui/jquery.easyui.min.js"
></script>
<script
type="text/javascript"
src="../lib/easyui/locale/easyui-lang-zh_CN.js"
></script>
</head>
<body>
<!-- 省略 -->
</body>
</html>

引入 easyui 后,需要使用哪种组件,可以查看相关文档或 API,十分简单,此处不一一赘述。

实战

引入 maven 依赖

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
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.2</version>
</dependency>
</dependencies>

使用 JPA

为了使用 JPA 技术访问数据,我们需要定义 Entity 和 Repository

定义一个 Entity:

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

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
private String phone;
private String email;

protected User() {}

public User(String firstName, String lastName, String phone, String email) {
this.firstName = firstName;
this.lastName = lastName;
this.phone = phone;
this.email = email;
}

// 略 getter/setter
}

定义一个 Repository:

1
2
3
4
public interface UserRepository extends CrudRepository<User, Long> {

List<User> findByLastName(String lastName);
}

使用 Web

首页 Controller,将 web 请求定向到指定页面(下面的例子定向到 index.html)

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

@RequestMapping(value = {"", "/", "index"})
public String index() {
return "index";
}

}

此外,需要定义一个 Controller,提供后台的 API 接口

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

@Autowired
private UserRepository customerRepository;

@RequestMapping(value = "/user", method = RequestMethod.GET)
public String user() {
return "user";
}

@ResponseBody
@RequestMapping(value = "/user/list")
public ResponseDTO<User> list() {
Iterable<User> all = customerRepository.findAll();
List<User> list = IteratorUtils.toList(all.iterator());
return new ResponseDTO<>(true, list.size(), list);
}

@ResponseBody
@RequestMapping(value = "/user/add")
public ResponseDTO<User> add(User user) {
User result = customerRepository.save(user);
List<User> list = new ArrayList<>();
list.add(result);
return new ResponseDTO<>(true, 1, list);
}

@ResponseBody
@RequestMapping(value = "/user/save")
public ResponseDTO<User> save(@RequestParam("id") Long id, User user) {
user.setId(id);
customerRepository.save(user);
List<User> list = new ArrayList<>();
list.add(user);
return new ResponseDTO<>(true, 1, list);
}

@ResponseBody
@RequestMapping(value = "/user/delete")
public ResponseDTO delete(@RequestParam("id") Long id) {
customerRepository.deleteById(id);
return new ResponseDTO<>(true, null, null);
}

}

使用 EasyUI

接下来,我们要使用前面定义的后台接口,仅需要在 EasyUI API 中指定 url 即可。

请留意下面示例中的 url 字段,和实际接口是一一对应的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
<!DOCTYPE html>
<html>
<head>
<title>Complex Layout - jQuery EasyUI Demo</title>
<meta charset="UTF-8" />
<link
rel="stylesheet"
type="text/css"
href="../lib/easyui/themes/bootstrap/easyui.css"
/>
<link
rel="stylesheet"
type="text/css"
href="../lib/easyui/themes/icon.css"
/>
<link
rel="stylesheet"
type="text/css"
href="../lib/easyui/themes/color.css"
/>
<script type="text/javascript" src="../lib/easyui/jquery.min.js"></script>
<script
type="text/javascript"
src="../lib/easyui/jquery.easyui.min.js"
></script>
<script
type="text/javascript"
src="../lib/easyui/locale/easyui-lang-zh_CN.js"
></script>
<style type="text/css">
body {
font-family: microsoft yahei;
}
</style>
</head>
<body>
<div style="width:100%">
<h2>基本的 CRUD 应用</h2>
<p>数据来源于后台系统</p>

<table
id="dg"
title="Custom List"
class="easyui-datagrid"
url="/user/list"
toolbar="#toolbar"
pagination="true"
rownumbers="true"
fitColumns="true"
singleSelect="true"
>
<thead>
<tr>
<th field="id" width="50">ID</th>
<th field="firstName" width="50">First Name</th>
<th field="lastName" width="50">Last Name</th>
<th field="phone" width="50">Phone</th>
<th field="email" width="50">Email</th>
</tr>
</thead>
</table>
<div id="toolbar">
<a
href="javascript:void(0)"
class="easyui-linkbutton"
iconCls="icon-add"
plain="true"
onclick="newUser()"
>添加</a
>
<a
href="javascript:void(0)"
class="easyui-linkbutton"
iconCls="icon-edit"
plain="true"
onclick="editUser()"
>修改</a
>
<a
href="javascript:void(0)"
class="easyui-linkbutton"
iconCls="icon-remove"
plain="true"
onclick="destroyUser()"
>删除</a
>
</div>

<div
id="dlg"
class="easyui-dialog"
style="width:400px"
data-options="closed:true,modal:true,border:'thin',buttons:'#dlg-buttons'"
>
<form
id="fm"
method="post"
novalidate
style="margin:0;padding:20px 50px"
>
<h3>User Information</h3>
<div style="margin-bottom:10px">
<input
name="firstName"
class="easyui-textbox"
required="true"
label="First Name:"
style="width:100%"
/>
</div>
<div style="margin-bottom:10px">
<input
name="lastName"
class="easyui-textbox"
required="true"
label="Last Name:"
style="width:100%"
/>
</div>
<div style="margin-bottom:10px">
<input
name="phone"
class="easyui-textbox"
required="true"
label="Phone:"
style="width:100%"
/>
</div>
<div style="margin-bottom:10px">
<input
name="email"
class="easyui-textbox"
required="true"
validType="email"
label="Email:"
style="width:100%"
/>
</div>
</form>
</div>
<div id="dlg-buttons">
<a
href="javascript:void(0)"
class="easyui-linkbutton c6"
iconCls="icon-ok"
onclick="saveUser()"
style="width:90px"
>Save</a
>
<a
href="javascript:void(0)"
class="easyui-linkbutton"
iconCls="icon-cancel"
onclick="javascript:$('#dlg').dialog('close')"
style="width:90px"
>Cancel</a
>
</div>
</div>

<script type="text/javascript">
var url

function newUser() {
$('#dlg')
.dialog('open')
.dialog('center')
.dialog('setTitle', 'New User')
$('#fm').form('clear')
url = '/user/add'
}

function editUser() {
var row = $('#dg').datagrid('getSelected')
if (row) {
$('#dlg')
.dialog('open')
.dialog('center')
.dialog('setTitle', 'Edit User')
$('#fm').form('load', row)
url = '/user/save'
}
}

function saveUser() {
$('#fm').form('submit', {
url: url,
onSubmit: function() {
return $(this).form('validate')
},
success: function(result) {
var result = eval('(' + result + ')')
if (result.errorMsg) {
$.messager.show({
title: 'Error',
msg: result.errorMsg
})
} else {
$('#dlg').dialog('close') // close the dialog
$('#dg').datagrid('reload') // reload the user data
}
}
})
}

function destroyUser() {
var row = $('#dg').datagrid('getSelected')
if (row) {
$.messager.confirm(
'Confirm',
'Are you sure you want to destroy this user?',
function(r) {
if (r) {
$.post(
'/user/delete',
{ id: row.id },
function(result) {
if (result.success) {
$('#dg').datagrid('reload') // reload the user data
} else {
$.messager.show({
// show error message
title: 'Error',
msg: result.errorMsg
})
}
},
'json'
)
}
}
)
}
}
</script>
</body>
</html>

完整示例

请参考 源码

运行方式:

1
2
mvn clean package -DskipTests=true
java -jar target/

在浏览器中访问:http://localhost:8080/

引用和引申

SpringBoot 之集成 Json

简介

Spring Boot 支持的 Json 库

Spring Boot 支持三种 Json 库:

  • Gson
  • Jackson
  • JSON-B

Jackson 是 Spring Boot 官方推荐的默认库。

Spring Boot 提供了 Jackson 的自动配置,Jackson 是 spring-boot-starter-json 的一部分。当 Jackson 在类路径上时,会自动配置 ObjectMapper bean。

Spring Boot 提供了 Gson 的自动配置。当 Gson 在 classpath 上时,会自动配置 Gson bean。提供了几个 spring.gson.* 配置属性来自定义配置。为了获得更多控制,可以使用一个或多个 GsonBuilderCustomizer bean。

Spring Boot 提供了 JSON-B 的自动配置。当 JSON-B API 在 classpath 上时,将自动配置 Jsonb bean。首选的 JSON-B 实现是 Apache Johnzon,它提供了依赖关系管理。

Spring Web 中的序列化、反序列化

以下注解都是 spring-web 中提供的支持。

@ResponseBody

@Responsebody 注解用于将 Controller 的方法返回的对象,通过适当的 HttpMessageConverter 转换为指定格式后,写入到 HTTP Response 对象的 body 数据区。一般在异步获取数据时使用。通常是在使用 @RequestMapping 后,返回值通常解析为跳转路径,加上 @Responsebody 后返回结果不会被解析为跳转路径,而是直接写入 HTTP 响应正文中。

示例:

1
2
3
4
5
@ResponseBody
@RequestMapping(name = "/getInfo", method = RequestMethod.GET)
public InfoDTO getInfo() {
return new InfoDTO();
}

@RequestBody

@RequestBody 注解用于读取 HTTP Request 请求的 body 部分数据,使用系统默认配置的 HttpMessageConverter 进行解析,然后把相应的数据绑定到要返回的对象上;再把 HttpMessageConverter 返回的对象数据绑定到 controller 中方法的参数上。

request 的 body 部分的数据编码格式由 header 部分的 Content-Type 指定。

示例:

1
2
3
4
@RequestMapping(name = "/postInfo", method = RequestMethod.POST)
public void postInfo(@RequestBody InfoDTO infoDTO) {
// ...
}

@RestController

Spring 4 以前:

如果需要返回到指定页面,则需要用 @Controller 配合视图解析器 InternalResourceViewResolver

如果需要返回 JSON,XML 或自定义 mediaType 内容到页面,则需要在对应的方法上加上 @ResponseBody 注解。

Spring 4 以后,新增了 @RestController 注解:

它相当于 @Controller + @RequestBody

如果使用 @RestController 注解 Controller,则 Controller 中的方法无法返回 jsp 页面,或者 html,配置的视图解析器 InternalResourceViewResolver 将不起作用,直接返回内容。

指定类的 Json 序列化、反序列化

如果使用 Jackson 序列化和反序列化 JSON 数据,您可能需要编写自己的 JsonSerializerJsonDeserializer 类。自定义序列化程序通常通过模块向 Jackson 注册,但 Spring Boot 提供了另一种 @JsonComponent 注释,可以更容易地直接注册 Spring Beans。

您可以直接在 JsonSerializerJsonDeserializer 实现上使用 @JsonComponent 注释。您还可以在包含序列化程序/反序列化程序作为内部类的类上使用它,如以下示例所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.io.*;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import org.springframework.boot.jackson.*;

@JsonComponent
public class Example {

public static class Serializer extends JsonSerializer<SomeObject> {
// ...
}

public static class Deserializer extends JsonDeserializer<SomeObject> {
// ...
}

}

ApplicationContext 中的所有 @JsonComponent bean 都会自动注册到 Jackson。因为 @JsonComponent 是使用 @Component 进行元注释的,所以通常的组件扫描规则适用。

Spring Boot 还提供了 JsonObjectSerializerJsonObjectDeserializer 基类,它们在序列化对象时提供了标准 Jackson 版本的有用替代方法。有关详细信息,请参阅 Javadoc 中的 JsonObjectSerializerJsonObjectDeserializer

@JsonTest

使用 @JsonTest 可以很方便的在 Spring Boot 中测试序列化、反序列化。

使用 @JsonTest 相当于使用以下自动配置:

1
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration org.springframework.boot.test.autoconfigure.json.JsonTestersAutoConfiguration

@JsonTest 使用示例:

想试试完整示例,可以参考:源码

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
@JsonTest
@RunWith(SpringRunner.class)
public class SimpleJsonTest {

private final Logger log = LoggerFactory.getLogger(this.getClass());

@Autowired
private JacksonTester<InfoDTO> json;

@Test
public void testSerialize() throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
InfoDTO infoDTO = new InfoDTO("JSON测试应用", "1.0.0", sdf.parse("2019-01-01 12:00:00"));
JsonContent<InfoDTO> jsonContent = json.write(infoDTO);
log.info("json content: {}", jsonContent.getJson());
// 或者使用基于JSON path的校验
assertThat(jsonContent).hasJsonPathStringValue("@.appName");
assertThat(jsonContent).extractingJsonPathStringValue("@.appName").isEqualTo("JSON测试应用");
assertThat(jsonContent).hasJsonPathStringValue("@.version");
assertThat(jsonContent).extractingJsonPathStringValue("@.version").isEqualTo("1.0.0");
assertThat(jsonContent).hasJsonPathStringValue("@.date");
assertThat(jsonContent).extractingJsonPathStringValue("@.date").isEqualTo("2019-01-01 12:00:00");
}

@Test
public void testDeserialize() throws Exception {
String content = "{\"appName\":\"JSON测试应用\",\"version\":\"1.0.0\",\"date\":\"2019-01-01\"}";
InfoDTO actual = json.parseObject(content);
assertThat(actual.getAppName()).isEqualTo("JSON测试应用");
assertThat(actual.getVersion()).isEqualTo("1.0.0");
}
}

Spring Boot 中的 json 配置

Jackson 配置

当 Spring Boot 的 json 库为 jackson 时,可以使用以下配置属性(对应 JacksonProperties 类):

1
2
3
4
5
6
7
8
9
10
11
12
spring.jackson.date-format= # Date format string or a fully-qualified date format class name. For instance, `yyyy-MM-dd HH:mm:ss`.
spring.jackson.default-property-inclusion= # Controls the inclusion of properties during serialization. Configured with one of the values in Jackson's JsonInclude.Include enumeration.
spring.jackson.deserialization.*= # Jackson on/off features that affect the way Java objects are deserialized.
spring.jackson.generator.*= # Jackson on/off features for generators.
spring.jackson.joda-date-time-format= # Joda date time format string. If not configured, "date-format" is used as a fallback if it is configured with a format string.
spring.jackson.locale= # Locale used for formatting.
spring.jackson.mapper.*= # Jackson general purpose on/off features.
spring.jackson.parser.*= # Jackson on/off features for parsers.
spring.jackson.property-naming-strategy= # One of the constants on Jackson's PropertyNamingStrategy. Can also be a fully-qualified class name of a PropertyNamingStrategy subclass.
spring.jackson.serialization.*= # Jackson on/off features that affect the way Java objects are serialized.
spring.jackson.time-zone= # Time zone used when formatting dates. For instance, "America/Los_Angeles" or "GMT+10".
spring.jackson.visibility.*= # Jackson visibility thresholds that can be used to limit which methods (and fields) are auto-detected.

GSON 配置

当 Spring Boot 的 json 库为 gson 时,可以使用以下配置属性(对应 GsonProperties 类):

1
2
3
4
5
6
7
8
9
10
11
spring.gson.date-format= # Format to use when serializing Date objects.
spring.gson.disable-html-escaping= # Whether to disable the escaping of HTML characters such as '<', '>', etc.
spring.gson.disable-inner-class-serialization= # Whether to exclude inner classes during serialization.
spring.gson.enable-complex-map-key-serialization= # Whether to enable serialization of complex map keys (i.e. non-primitives).
spring.gson.exclude-fields-without-expose-annotation= # Whether to exclude all fields from consideration for serialization or deserialization that do not have the "Expose" annotation.
spring.gson.field-naming-policy= # Naming policy that should be applied to an object's field during serialization and deserialization.
spring.gson.generate-non-executable-json= # Whether to generate non executable JSON by prefixing the output with some special text.
spring.gson.lenient= # Whether to be lenient about parsing JSON that doesn't conform to RFC 4627.
spring.gson.long-serialization-policy= # Serialization policy for Long and long types.
spring.gson.pretty-printing= # Whether to output serialized JSON that fits in a page for pretty printing.
spring.gson.serialize-nulls= # Whether to serialize null fields.

Spring Boot 中使用 Fastjson

国内很多的 Java 程序员更喜欢使用阿里的 fastjson 作为 json lib。那么,如何在 Spring Boot 中将其替换默认的 jackson 库呢?

你需要做如下处理:

(1)引入 fastjson jar 包:

1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.54</version>
</dependency>

(2)实现 WebMvcConfigurer 接口,自定义 configureMessageConverters 接口。如下所示:

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
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

private final Logger log = LoggerFactory.getLogger(this.getClass());

/**
* 自定义消息转换器
* @param converters
*/
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// 清除默认 Json 转换器
converters.removeIf(converter -> converter instanceof MappingJackson2HttpMessageConverter);

// 配置 FastJson
FastJsonConfig config = new FastJsonConfig();
config.setSerializerFeatures(SerializerFeature.QuoteFieldNames, SerializerFeature.WriteEnumUsingToString,
SerializerFeature.WriteMapNullValue, SerializerFeature.WriteDateUseDateFormat,
SerializerFeature.DisableCircularReferenceDetect);

// 添加 FastJsonHttpMessageConverter
FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
fastJsonHttpMessageConverter.setFastJsonConfig(config);
List<MediaType> fastMediaTypes = new ArrayList<>();
fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
fastJsonHttpMessageConverter.setSupportedMediaTypes(fastMediaTypes);
converters.add(fastJsonHttpMessageConverter);

// 添加 StringHttpMessageConverter,解决中文乱码问题
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(Charset.forName("UTF-8"));
converters.add(stringHttpMessageConverter);
}

// ...
}

示例源码

完整示例:源码

引申和引用

引申

引用

Spring 访问 Elasticsearch

简介

Elasticsearch 是一个开源的、分布式的搜索和分析引擎。

通过 REST 客户端连接 Elasticsearch

如果在 classpath 路径下存在 org.elasticsearch.client:elasticsearch-rest-client jar 包,Spring Boot 会自动配置并注册一个 RestClient Bean,它的默认访问路径为:localhost:9200

你可以使用如下方式进行定制:

1
2
3
spring.elasticsearch.rest.uris=http://search.example.com:9200
spring.elasticsearch.rest.username=user
spring.elasticsearch.rest.password=secret

您还可以注册实现任意数量的 RestClientBuilderCustomizer bean,以进行更高级的定制。要完全控制注册,请定义 RestClient bean。

如果 classpath 路径有 org.elasticsearch.client:elasticsearch-rest-high-level-client jar 包,Spring Boot 将自动配置一个 RestHighLevelClient,它包装任何现有的 RestClient bean,重用其 HTTP 配置。

通过 Jest 连接 Elasticsearch

如果 classpath 上有 Jest,你可以注入一个自动配置的 JestClient,默认情况下是 localhost:9200。您可以进一步调整客户端的配置方式,如以下示例所示:

1
2
3
4
spring.elasticsearch.jest.uris=http://search.example.com:9200
spring.elasticsearch.jest.read-timeout=10000
spring.elasticsearch.jest.username=user
spring.elasticsearch.jest.password=secret

您还可以注册实现任意数量的 HttpClientConfigBuilderCustomizer bean,以进行更高级的定制。以下示例调整为其他 HTTP 设置:

1
2
3
4
5
6
7
8
static class HttpSettingsCustomizer implements HttpClientConfigBuilderCustomizer {

@Override
public void customize(HttpClientConfig.Builder builder) {
builder.maxTotalConnection(100).defaultMaxTotalConnectionPerRoute(5);
}

}

要完全控制注册,请定义 JestClient bean。

通过 Spring Data 访问 Elasticsearch

要连接到 Elasticsearch,您必须提供一个或多个集群节点的地址。可以通过将 spring.data.elasticsearch.cluster-nodes 属性设置为以逗号分隔的 host:port 列表来指定地址。使用此配置,可以像任何其他 Spring bean 一样注入 ElasticsearchTemplateTransportClient,如以下示例所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
spring.data.elasticsearch.cluster-nodes=localhost:9300
@Component
public class MyBean {

private final ElasticsearchTemplate template;

public MyBean(ElasticsearchTemplate template) {
this.template = template;
}

// ...

}

如果你添加了自定义的 ElasticsearchTemplateTransportClient @Bean ,就会替换默认的配置。

Elasticsearch Repositories

Spring Data 包含对 Elasticsearch 的 repository 支持。基本原则是根据方法名称自动为您构建查询。

事实上,Spring Data JPA 和 Spring Data Elasticsearch 共享相同的通用基础架构。

源码

完整示例:源码

使用方法:

1
2
3
mvn clean package
cd target
java -jar spring-boot-data-elasticsearch.jar

版本

Spring 和 Elasticsearch 匹配版本:

Spring Data Elasticsearch Elasticsearch Spring Framework Spring Boot
5.0.x 8.5.3 6.0.x 3.0.x
4.4.x 7.17.3 5.3.x 2.7.x
4.3.x 7.15.2 5.3.x 2.6.x
4.2.x[1] 7.12.0 5.3.x 2.5.x
4.1.x[1] 7.9.3 5.3.2 2.4.x
4.0.x[1] 7.6.2 5.2.12 2.3.x
3.2.x[1] 6.8.12 5.2.12 2.2.x
3.1.x[1] 6.2.2 5.1.19 2.1.x
3.0.x[1] 5.5.0 5.0.13 2.0.x
2.1.x[1] 2.4.0 4.3.25 1.5.x

参考资料

SpringBoot 之 banner 定制

简介

Spring Boot 启动时默认会显示以下 LOGO:

1
2
3
4
5
6
7
  .   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.1.1.RELEASE)

实际上,Spring Boot 支持自定义 logo 的功能。

让我们来看看如何实现的。

只要你在 resources 目录下放置名为 banner.txtbanner.gifbanner.jpgbanner.png 的文件,Spring Boot 会自动加载,将其作为启动时打印的 logo。

  • 对于文本文件,Spring Boot 会将其直接输出。
  • 对于图像文件( banner.gifbanner.jpgbanner.png ),Spring Boot 会将图像转为 ASCII 字符,然后输出。

变量

banner.txt 文件中还可以使用变量来设置字体、颜色、版本号。

变量 描述
${application.version} MANIFEST.MF 中定义的版本。如:1.0
${application.formatted-version} MANIFEST.MF 中定义的版本,并添加一个 v 前缀。如:v1.0
${spring-boot.version} Spring Boot 版本。如:2.1.1.RELEASE.
${spring-boot.formatted-version} Spring Boot 版本,并添加一个 v 前缀。如:v2.1.1.RELEASE
${Ansi.NAME} (or ${AnsiColor.NAME}, ${AnsiBackground.NAME}, ${AnsiStyle.NAME}) ANSI 颜色、字体。更多细节,参考:AnsiPropertySource
${application.title} MANIFEST.MF 中定义的应用名。

示例:

在 Spring Boot 项目中的 resources 目录下添加一个名为 banner.txt 的文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
${AnsiColor.BRIGHT_YELLOW}${AnsiStyle.BOLD}
________ ___ ___ ________ ___ __ ___ ___
|\ ___ \|\ \|\ \|\ ___ \|\ \ |\ \|\ \|\ \
\ \ \_|\ \ \ \\\ \ \ \\ \ \ \ \ \ \ \ \ \\\ \
\ \ \ \\ \ \ \\\ \ \ \\ \ \ \ \ __\ \ \ \ \\\ \
\ \ \_\\ \ \ \\\ \ \ \\ \ \ \ \|\__\_\ \ \ \\\ \
\ \_______\ \_______\ \__\\ \__\ \____________\ \_______\
\|_______|\|_______|\|__| \|__|\|____________|\|_______|
${AnsiBackground.WHITE}${AnsiColor.RED}${AnsiStyle.UNDERLINE}
:: Spring Boot :: (v${spring-boot.version})
:: Spring Boot Tutorial :: (v1.0.0)

注:${} 设置字体颜色的变量之间不能换行或空格分隔,否则会导致除最后一个变量外,都不生效。

启动应用后,控制台将打印如下 logo:

img
推荐两个生成字符画的网站,可以将生成的字符串放入这个banner.txt 文件:

配置

application.properties 中与 Banner 相关的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# banner 模式。有三种模式:console/log/off
# console 打印到控制台(通过 System.out)
# log - 打印到日志中
# off - 关闭打印
spring.main.banner-mode = off
# banner 文件编码
spring.banner.charset = UTF-8
# banner 文本文件路径
spring.banner.location = classpath:banner.txt
# banner 图像文件路径(可以选择 png,jpg,gif 文件)
spring.banner.image.location = classpath:banner.gif
used).
# 图像 banner 的宽度(字符数)
spring.banner.image.width = 76
# 图像 banner 的高度(字符数)
spring.banner.image.height =
# 图像 banner 的左边界(字符数)
spring.banner.image.margin = 2
# 是否将图像转为黑色控制台主题
spring.banner.image.invert = false

当然,你也可以在 YAML 文件中配置,例如:

1
2
3
spring:
main:
banner-mode: off

编程

默认,Spring Boot 会注册一个 SpringBootBanner 的单例 Bean,用来负责打印 Banner。

如果想完全个人定制 Banner,可以这么做:先实现 org.springframework.boot.Banner#printBanner 接口来自己定制 Banner。在将这个 Banner 通过 SpringApplication.setBanner(…) 方法注入 Spring Boot。

示例源码

示例源码:spring-boot-banner

参考资料

Spring 访问 MongoDB

简介

MongoDB 是一个基于分布式文件存储的数据库。由 C++ 语言编写。旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。MongoDB 将数据存储为一个文档,数据结构由键值对组成。MongoDB 文档类似于 JSON 对象。字段值可以包含其他文档,数组及文档数组。

在 Spring 中,spring-data-mongodb 项目对访问 MongoDB 进行了 API 封装,提供了便捷的访问方式。 Spring Data MongoDB 的核心是一个以 POJO 为中心的模型,用于与 MongoDB DBCollection 交互并轻松编写 Repository 样式的数据访问层。

spring-boot 项目中的子模块 spring-boot-starter-data-mongodb 基于 spring-data-mongodb 项目,做了二次封装,大大简化了 MongoDB 的相关配置。

Spring Boot 快速入门

引入依赖

在 pom.xml 中引入依赖:

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

数据源配置

1
2
3
4
5
spring.data.mongodb.host = localhost
spring.data.mongodb.port = 27017
spring.data.mongodb.database = test
spring.data.mongodb.username = root
spring.data.mongodb.password = root

定义实体

定义一个具有三个属性的 Customer 类:idfirstNamelastName

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import org.springframework.data.annotation.Id;

public class Customer {

@Id
public String id;

public String firstName;

public String lastName;

public Customer(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}

@Override
public String toString() {
return String.format(
"Customer[id=%s, firstName='%s', lastName='%s']",
id, firstName, lastName);
}

}

spring-data-mongodb 会将 Customer 类映射到一个名为 customer 的集合中。如果要更改集合的名称,可以在类上使用 @Document 注解。

创建 Repository

spring-data-mongodb 继承了 Spring Data Commons 项目的能力,所以可以使用其通用 API——Repository

先定义一个 CustomerRepository 类,继承 MongoRepository 接口,并指定其泛型参数:CustomerString。MongoRepository 接口支持多种操作,包括 CRUD 和分页查询。在下面的例子中,定义了两个查询方法:

1
2
3
4
5
6
7
8
9
10
import java.util.List;

import org.springframework.data.mongodb.repository.MongoRepository;

public interface CustomerRepository extends MongoRepository<Customer, String> {

Customer findByFirstName(String firstName);
List<Customer> findByLastName(String lastName);

}

创建 Application

创建一个 Spring Boot 的启动类 Application,并在启动的 main 方法中使用 CustomerRepository 实例访问 MongoDB。

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
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DataMongodbApplication implements CommandLineRunner {

@Autowired
private CustomerRepository repository;

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

@Override
public void run(String... args) {

repository.deleteAll();

// save a couple of customers
repository.save(new Customer("Alice", "Smith"));
repository.save(new Customer("Bob", "Smith"));

// fetch all customers
System.out.println("Customers found with findAll():");
System.out.println("-------------------------------");
for (Customer customer : repository.findAll()) {
System.out.println(customer);
}
System.out.println();

// fetch an individual customer
System.out.println("Customer found with findByFirstName('Alice'):");
System.out.println("--------------------------------");
System.out.println(repository.findByFirstName("Alice"));

System.out.println("Customers found with findByLastName('Smith'):");
System.out.println("--------------------------------");
for (Customer customer : repository.findByLastName("Smith")) {
System.out.println(customer);
}
}

}

运行 DataMongodbApplication 的 main 方法后,输出类似如下类容:

1
2
3
4
5
6
7
8
9
10
11
12
Customers found with findAll():
-------------------------------
Customer(id=63d6157b265e7c5e48077f63, firstName=Alice, lastName=Smith)
Customer(id=63d6157b265e7c5e48077f64, firstName=Bob, lastName=Smith)

Customer found with findByFirstName('Alice'):
--------------------------------
Customer(id=63d6157b265e7c5e48077f63, firstName=Alice, lastName=Smith)
Customers found with findByLastName('Smith'):
--------------------------------
Customer(id=63d6157b265e7c5e48077f63, firstName=Alice, lastName=Smith)
Customer(id=63d6157b265e7c5e48077f64, firstName=Bob, lastName=Smith)

示例源码

更多 Spring 访问 MongoDB 示例请参考:MongoDB 示例源码

参考资料

技术文档规范

文档采用 Markdown 语法书写。

📚 “参考”Markdown 语法可以参考:

1. 标题

1.1. 标题层级

标题分为四级。

  1. 一级标题:文章的标题
  2. 二级标题:文章内容的大标题
  3. 三级标题:二级标题下一级的标题
  4. 四级标题:三级标题下一级的标题

1.2. 标题原则

  • 一篇文章中应该尽力避免同名标题。
  • 一级标题下,不能直接出现三级标题。
  • 标题要避免孤立编号(即同级标题只有一个)。
  • 下级标题不重复上一级标题的内容。
  • 谨慎使用四级标题,尽量避免出现,保持层级的简单和防止出现过于复杂的章节。如果三级标题下有并列性的内容,建议只使用项目列表(Item list)。

2. 文本

2.1. 字间距

全角中文字符与半角英文字符之间,应有一个半角空格。

1
2
3
反例:本文介绍如何快速启动Windows系统。

正例:本文介绍如何快速启动 Windows 系统。

全角中文字符与半角阿拉伯数字之间,有没有半角空格都可,但必须保证风格统一,不能两种风格混杂。

1
2
3
正例:2011年5月15日,我订购了5台笔记本电脑与10台平板电脑。

正例:2011 年 5 15 日,我订购了 5 台笔记本电脑与 10 台平板电脑。

半角的百分号,视同阿拉伯数字。

英文单位若不翻译,单位前的阿拉伯数字与单位间不留空格。

1
2
3
反例:一部容量为 16 GB 的智能手机

正例:一部容量为 16GB 的智能手机

半角英文字符和半角阿拉伯数字,与全角标点符号之间不留空格。

1
2
3
反例:他的电脑是 MacBook Air 。

正例:他的电脑是 MacBook Air。

2.2. 句子

  • 避免使用长句。一个句子建议不超过 100 字或者正文的 3 行。
  • 尽量使用简单句和并列句,避免使用复合句。

2.3. 写作风格

尽量不使用被动语态,改为使用主动语态。

1
2
3
反例:假如此软件尚未被安装,

正例:假如尚未安装这个软件,

不使用非正式的语言风格。

1
2
3
反例:Lady Gaga 的演唱会真是酷毙了,从没看过这么给力的表演!!!

正例:无法参加本次活动,我深感遗憾。

用对“的”、“地”、“得”。

1
2
3
4
5
6
7
8
她露出了开心的笑容。
(形容词+的+名词)

她开心地笑了。
(副词+地+动词)

她笑得很开心。
(动词+得+副词)

使用代词时(比如“其”、“该”、“此”、“这”等词),必须明确指代的内容,保证只有一个含义。

1
2
3
反例:从管理系统可以监视中继系统和受其直接控制的分配系统。

正例:从管理系统可以监视两个系统:中继系统和受中继系统直接控制的分配系统。

名词前不要使用过多的形容词。

1
2
3
反例:此设备的使用必须在接受过本公司举办的正式的设备培训的技师的指导下进行。

正例:此设备必须在技师的指导下使用,且指导技师必须接受过由本公司举办的正式设备培训。

单个句子的长度尽量保持在 20 个字以内;20 ~ 29 个字的句子,可以接受;30 ~ 39 个字的句子,语义必须明确,才能接受;多于 40 个字的句子,在任何情况下都不能接受。

1
2
3
反例:本产品适用于从由一台服务器进行动作控制的单一节点结构到由多台服务器进行动作控制的并行处理程序结构等多种体系结构。

正例:本产品适用于多种体系结构。无论是由一台服务器(单一节点结构),还是由多台服务器(并行处理结构)进行动作控制,均可以使用本产品。

同样一个意思,尽量使用肯定句表达,不使用否定句表达。

1
2
3
反例:请确认没有接通装置的电源。

正例:请确认装置的电源已关闭。

避免使用双重否定句。

1
2
3
反例:没有删除权限的用户,不能删除此文件。

正例:用户必须拥有删除权限,才能删除此文件。

2.4. 英文处理

英文原文如果使用了复数形式,翻译成中文时,应该将其还原为单数形式。

1
2
3
英文:⋯information stored in random access memory (RAMs)⋯

中文:……存储在随机存取存储器(RAM)里的信息……

外文缩写可以使用半角圆点(.)表示缩写。

1
2
U.S.A.
Apple, Inc.

表示中文时,英文省略号()应改为中文省略号(……)。

1
2
3
英文:5 minutes later

中文:5 分钟过去了⋯⋯

英文书名或电影名改用中文表达时,双引号应改为书名号。

1
2
3
英文:He published an article entitled "The Future of the Aviation".

中文:他发表了一篇名为《航空业的未来》的文章。

第一次出现英文词汇时,在括号中给出中文标注。此后再次出现时,直接使用英文缩写即可。

1
IOC(International Olympic Committee,国际奥林匹克委员会)。这样定义后,便可以直接使用“IOC”了。

专有名词中每个词第一个字母均应大写,非专有名词则不需要大写。

1
2
3
“American Association of Physicists in Medicine”(美国医学物理学家协会)是专有名词,需要大写。

“online transaction processing”(在线事务处理)不是专有名词,不应大写。

3. 段落

3.1. 段落原则

  • 一个段落只能有一个主题,或一个中心句子。
  • 段落的中心句子放在段首,对全段内容进行概述。后面陈述的句子为核心句服务。
  • 一个段落的长度不能超过七行,最佳段落长度小于等于四行。
  • 段落的句子语气要使用陈述和肯定语气,避免使用感叹语气。
  • 段落之间使用一个空行隔开。
  • 段落开头不要留出空白字符。

3.2. 引用

引用第三方内容时,应注明出处。

1
One man’s constant is another man’s variable. — Alan Perlis

如果是全篇转载,请在全文开头显著位置注明作者和出处,并链接至原文。

1
本文转载自 WikiQuote

使用外部图片时,必须在图片下方或文末标明来源。

1
本文部分图片来自 Wikipedia

3.3. 强调

一些特殊的强调内容可以按照如下方式书写:

🔔 “注意”

💡 “提示”

📚 “参考”

4. 数值

4.1. 半角数字

数字一律使用半角形式,不得使用全角形式。

1
2
3
反例: 这件商品的价格是1000元。

正例: 这件商品的价格是 1000 元。

4.2. 千分号

数值为千位以上,应添加千分号(半角逗号)。

1
XXX 公司的实收资本为 RMB1,258,000

对于 4 ~ 6 位的数值,千分号是选用的,比如10001,000都可以接受。对于 7 位及以上的数值,千分号是必须的。

多位小数要从小数点后从左向右添加千分号,比如4.234,345

4.3. 货币

货币应为阿拉伯数字,并在数字前写出货币符号,或在数字后写出货币中文名称。

1
2
$1,000
1,000 美元

4.4. 数值范围

表示数值范围时,用连接。参见《标点符号》一节的“连接号”部分。

带有单位或百分号时,两个数字都要加上单位或百分号,不能只加后面一个。

1
2
3
4
5
反例:132234kg
正例:132kg~234kg

反例:6789%
正例:67%~89%

4.5. 变化程度的表示法

数字的增加要使用“增加了”、“增加到”。“了”表示增量,“到”表示定量。

1
2
3
4
5
增加到过去的两倍
(过去为一,现在为二)

增加了两倍
(过去为一,现在为三)

数字的减少要使用“降低了”、“降低到”。“了”表示增量,“到”表示定量。

1
2
3
4
5
降低到百分之八十
(定额是一百,现在是八十)

降低了百分之八十
(原来是一百,现在是二十)

不能用“降低 N 倍”或“减少 N 倍”的表示法,要用“降低百分之几”或“减少百分之几”。因为减少(或降低)一倍表示数值原来为一百,现在等于零。

5. 符号

5.1. 符号原则

  • 中文语句的标点符号,均应该采取全角符号,这样可以保证视觉的一致。
  • 如果整句为英文,则该句使用英文/半角标点。
  • 句号、问号、叹号、逗号、顿号、分号和冒号不得出现在一行之首。

5.2. 句号

中文语句中的结尾处应该用全角句号()。

句子末尾用括号加注时,句号应在括号之外。

1
2
3
反例:关于文件的输出,请参照第 1.3 节(见第 26 页。)

正例:关于文件的输出,请参照第 1.3 节(见第 26 页)。

5.3. 逗号

逗号表示句子内部的一般性停顿。

注意避免“一逗到底”,即整个段落除了结尾,全部停顿都使用逗号。

5.4. 顿号

句子内部的并列词,应该用全角顿号() 分隔,而不用逗号,即使并列词是英语也是如此。

1
2
3
反例:我最欣赏的科技公司有 Google, Facebook, 腾讯, 阿里和百度等。

正例:我最欣赏的科技公司有 Google、Facebook、腾讯、阿里和百度等。

英文句子中,并列词语之间使用半角逗号(,)分隔。

1
例句:Microsoft Office includes Word, Excel, PowerPoint, Outlook and other components.

5.5. 分号

分号表示复句内部并列分句之间的停顿。

5.6. 引号

引用时,应该使用全角双引号(“ ”),注意前后双引号不同。

1
例句:许多人都认为客户服务的核心是“友好”和“专业”。

引号里面还要用引号时,外面一层用双引号,里面一层用单引号(‘ ’),注意前后单引号不同。

1
例句:鲍勃解释道:“我要放音乐,可萨利说,‘不行!’。”

5.7. 圆括号

补充说明时,使用全角圆括号(),括号前后不加空格。

1
例句:请确认所有的连接(电缆和接插件)均安装牢固。

5.8. 冒号

全角冒号()常用在需要解释的词语后边,引出解释和说明。

1
例句:请确认以下几项内容:时间、地点、活动名称,以及来宾数量。

表示时间时,应使用半角冒号(:)。

1
例句:早上 8:00

5.9. 省略号

省略号……表示语句未完、或者语气的不连续。它占两个汉字空间、包含六个省略点,不要使用。。。...等非标准形式。

省略号不应与“等”这个词一起使用。

1
2
3
4
5
反例:我们为会餐准备了香蕉、苹果、梨…等各色水果。

正例:我们为会餐准备了各色水果,有香蕉、苹果、梨……

正例:我们为会餐准备了香蕉、苹果、梨等各色水果。

5.10. 感叹号

应该使用平静的语气叙述,尽量避免使用感叹号

不得多个感叹号连用,比如!!!!!

5.11. 破折号

破折号————一般用于做进一步解释。破折号应占两个汉字的位置。

1
例句:直觉————尽管它并不总是可靠的————告诉我,这事可能出了些问题。

5.12. 连接号

连接号用于连接两个类似的词。

以下场合应该使用直线连接号(-),占一个半角字符的位置。

  • 两个名词的复合
  • 图表编号
1
2
3
例句:氧化-还原反应

例句:图 1-1

以下场合应该使用波浪连接号(),占一个全角字符的位置。

  • 数值范围(例如日期、时间或数字)
1
例句:2009 年~2011 年

注意,波浪连接号前后两个值都应该加上单位。

波浪连接号也可以用汉字“至”代替。

1
例句:周围温度:-20°C-10°C

6. 结构

6.1. 目录结构

技术手册目录结构是一部完整的书,建议采用下面的结构。

  • 简介(Introduction) - [必选][目录|文件] 提供对产品和文档本身的总体的、扼要的说明
  • 入门篇(Quickstart) - [可选][文件] 如何最快速地使用产品
  • 基础篇(Basics) - [必选][目录] 又称”使用篇“,提供初级的使用教程
    • 环境准备(Prerequisite) - [可选][文件] 软件使用需要满足的前置条件
    • 安装(Installation) - [可选][文件] 软件的安装方法
    • 配置(Configuration) - [可选][目录|文件] 软件的配置
    • 特性(Feature) - [必选][目录|文件] 软件的功能特性
  • 进阶篇(Advanced) - [可选][目录] 又称”开发篇“,提供中高级的开发教程
    • 原理(Principle) - [可选][目录|文件] 软件的原理
    • 设计(Design) - [可选][目录|文件] 软件的设计,如:架构、设计思想等
  • 实战篇(Action) - [可选][目录] 提供一些具有实战意义的示例说明
  • API(API) - [可选][目录|文件] 软件 API 的逐一介绍
  • 常见问题(FAQ) - [可选][目录|文件] 常见问题解答
  • 附录(Appendix) - [可选][目录] 不属于教程本身、但对阅读教程有帮助的内容
    • 命令(Command) - [可选][目录] 命令
    • 资源(Resource) - [必选][文件] 资源
    • 术语(Glossary) - [可选][文件] 名词解释
    • 技巧(Recipe) - [可选][文件] 最佳实践
    • 版本(Changelog) - [可选][文件] 版本说明
    • 反馈(Feedback) - [可选][文件] 反馈方式

下面是两个真实范例,可参考。

6.2. 文件名

文档的文件名不得含有空格。

文件名必须使用半角字符,不得使用全角字符。这也意味着,中文不能用于文件名。

1
2
3
反例: 名词解释.md

正例: glossary.md

文件名建议只使用小写字母,不使用大写字母。

1
2
3
反例:TroubleShooting.md

正例:troubleshooting.md

为了醒目,某些说明文件的文件名,可以使用大写字母,比如READMELICENSE

文件名包含多个单词时,单词之间建议使用半角的连词线(-)分隔。

1
2
3
反例:advanced_usage.md

正例:advanced-usage.md

7. Emoji

在 markdown 文档中,普遍会使用 emoji,帮助理解内容。但是,如果滥用 emoji,可能会适得其反。

这里,将一些比较约定俗成的 emoji 表情使用场景列举一下:

  • 💡 提示 - [推荐]
  • 🔔 注意、警告 - [推荐]
  • ⭕ 正确 - [推荐]
  • ❌ 错误 - [推荐]
  • ❓ 问题 - [推荐]
  • ⛔ 禁止 - [推荐]
  • 🚧 未完待续、有待补充 - [推荐]
  • 📚 参考、参考资料 - [可选]
  • ⌨ 源码 - [可选]

8. 参考

个人目录管理规范

作为程序员,想必每个人都会有大量的资料、数据。按照条理清晰的目录结构去分类化存储,十分有助于管理文件。

目录结构

以下是我个人整理的目录结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.
├── Codes #代码目录
│ ├── Other #第三方代码目录
│ ├── My #个人代码目录
│ └── Work #工作代码目录
├── Data #数据目录
├── Downloads #下载文件目录
├── Docs #文档目录
│ ├── Books #电子书目录
│ ├── My #个人文档目录
│ └── Work #工作文档目录
├── Movies #视频目录
├── Music #音乐目录
├── Pictures #图片目录
├── Public #共享目录
├── Temp #临时文件目录
└── Tools #工具软件目录
└── Packages #安装包目录

注:如果您使用的操作系统是 Mac 这种可以为目录或文件添加 tag 的操作系统,那么您可以根据自己的喜好更细致化的管理。

2. 文件管理软件

选用便利的文件管理软件,可以让你的文件管理如虎添翼。这里推荐几款经典的文件管理工具。

2.1. Clover

Clover 是 Windows Explorer 资源管理器的一个扩展,为其增加类似谷歌 Chrome 浏览器的多标签页功能。

2.2. Everything

Everything 可以立即在 windows 系统中找到制定名称的文件和文件夹。

2.3. Wox

Wox 是一款简单易用的 Windows 启动器。可以把它视为 windows 版的 Alfred。

2.4. Q-dir

Q-dir 是轻量的文件管理器,特点鲜明,各种布局视图切换灵活,默认四个小窗口组成一个大窗口,操作快捷。

Maven 实战问题和最佳实践

Maven 常见问题

dependencies 和 dependencyManagement,plugins 和 pluginManagement 有什么区别

dependencyManagement 是表示依赖 jar 包的声明,即你在项目中的 dependencyManagement 下声明了依赖,maven 不会加载该依赖,dependencyManagement 声明可以被继承。

dependencyManagement 的一个使用案例是当有父子项目的时候,父项目中可以利用 dependencyManagement 声明子项目中需要用到的依赖 jar 包,之后,当某个或者某几个子项目需要加载该插件的时候,就可以在子项目中 dependencies 节点只配置 groupId 和 artifactId 就可以完成插件的引用。

dependencyManagement 主要是为了统一管理插件,确保所有子项目使用的插件版本保持一致,类似的还有 plugins 和 pluginManagement。

IDEA 修改 JDK 版本后编译报错

错误现象:

修改 JDK 版本,指定 maven-compiler-plugin 的 source 和 target 为 1.8 。

然后,在 Intellij IDEA 中执行 maven 指令,报错:

1
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.0:compile (default-compile) on project apollo-common: Fatal error compiling: 无效的目标版本: 1.8 -> [Help 1]

错误原因:

maven 的 JDK 源与指定的 JDK 编译版本不符。

排错手段:

  • 查看 Project Settings

Project SDK 是否正确

img

SDK 路径是否正确

img

  • 查看 Settings > Maven 的配置

JDK for importer 是否正确

img

Runner 是否正确

img

重复引入依赖

在 Idea 中,选中 Module,使用 Ctrl+Alt+Shift+U,打开依赖图,检索是否存在重复引用的情况。如果存在重复引用,可以将多余的引用删除。

如何打包一个可以直接运行的 Spring Boot jar 包

可以使用 spring-boot-maven-plugin 插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

如果引入了第三方 jar 包,如何打包?

首先,要添加依赖

1
2
3
4
5
6
7
<dependency>
<groupId>io.github.dunwu</groupId>
<artifactId>dunwu-common</artifactId>
<version>1.0.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/main/resources/lib/dunwu-common-1.0.0.jar</systemPath>
</dependency>

接着,需要配置 spring-boot-maven-plugin 插件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
<configuration>
<includeSystemScope>true</includeSystemScope>
</configuration>
</plugin>
</plugins>
</build>

去哪儿找 maven dependency

问:刚接触 maven 的新手,往往会有这样的疑问,我该去哪儿找 jar?

答:官方推荐的搜索 maven dependency 网址:

如何指定编码

问:众所周知,不同编码格式常常会产生意想不到的诡异问题,那么 maven 构建时如何指定 maven 构建时的编码?

答:在 properties 中指定 project.build.sourceEncoding

1
2
3
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

如何指定 JDK 版本

问:如何指定 maven 构建时的 JDK 版本

答:有两种方法:

(1)properties 方式

1
2
3
4
5
6
7
8
<project>
...
<properties>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
...
</project>

(2)使用 maven-compiler-plugin 插件,并指定 source 和 target 版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<build>
...
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
...
</build>

如何避免将 dependency 打包到构件中

答:指定 maven dependency 的 scope 为 provided,这意味着:依赖关系将在运行时由其容器或 JDK 提供。
具有此范围的依赖关系不会传递,也不会捆绑在诸如 WAR 之类的包中,也不会包含在运行时类路径中。

如何跳过单元测试

问:执行 mvn package 或 mvn install 时,会自动编译所有单元测试(src/test/java 目录下的代码),如何跳过这一步?

答:在执行命令的后面,添加命令行参数 -Dmaven.test.skip=true 或者 -DskipTests=true

如何引入本地 jar

问:有时候,需要引入在中央仓库找不到的 jar,但又想通过 maven 进行管理,那么应该如何做到呢?
答:可以通过设置 dependency 的 scope 为 system 来引入本地 jar。
例:

  • 将私有 jar 放置在 resouces/lib 下,然后以如下方式添加依赖:
  • groupId 和 artifactId 可以按照 jar 包中的 package 设置,只要和其他 jar 不冲突即可。
1
2
3
4
5
6
7
<dependency>
<groupId>xxx</groupId>
<artifactId>xxx</artifactId>
<version>1.0.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/main/resources/lib/xxx-6.0.0.jar</systemPath>
</dependency>

如何排除依赖

问:如何排除依赖一个依赖关系?比方项目中使用的 libA 依赖某个库的 1.0 版。libB 以来某个库的 2.0 版,如今想统一使用 2.0 版,怎样去掉 1.0 版的依赖?

答:通过 exclusion 排除指定依赖即可。

例:

1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.12</version>
<optional>true</optional>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>

Maven 最佳实践

通过 bom 统一管理版本

采用类似 spring-boot-dependencies 的方式统一管理依赖版本。

spring-boot-dependencies 的 pom.xml 形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.9.RELEASE</version>
<packaging>pom</packaging>

<!-- 省略 -->

<!-- 依赖包版本管理 -->
<dependencyManagement>
<dependencies>
<!-- 省略 -->
</dependencies>
</dependencyManagement>

<build>
<!-- 插件版本管理 -->
<pluginManagement>
<plugins>
<!-- 省略 -->
</plugins>
</pluginManagement>
</build>
</project>

其他项目引入 spring-boot-dependencies 来管理依赖版本的方式:

1
2
3
4
5
6
7
8
9
10
11
 <dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

UML 结构建模图

结构图定义了一个模型的静态架构。它们通常被用来对那些构成模型的‘要素’建模,诸如:类,对象,接口和物理组件。另外,它们也被用来对元素间关联和依赖关系进行建模。

关键词:部署图, 组件图, 包图, 类图, 复合结构图, 对象图

部署图

部署图(Deployment Diagram)用于对系统的物理结构建模。部署图将显示系统中的软件组件和硬件组件之间的关系以及处理工作的物理分布。

节点

节点既可以是硬件元素,也可以是软件元素。它显示为一个立方体,如下图所示。

节点实例

图可以显示节点实例,实例与节点的区分是:实例的名称带下划线,冒号放在它的基本节点类型之前。实例在冒号之前可以有名称,也可以没有名称。下图显示了一个具名的计算机实例。

节点构造型

为节点提供了许多标准的构造型,分别命名为 «cdrom», «cd-rom», «computer», «disk array», «pc», «pc client», «pc server», «secure», «server», «storage», «unix server», «user pc»。 并在节点符号的右上角显示适当的图标。

工件

工件是软件开发过程中的产品。包括过程模型(如:用例模型,设计模型等),源文件,执行文件,设计文档,测试报告,构造型,用户手册等等。

工件表示为带有工件名称的矩形,并显示«artifact»关键字和文档符号。

关联

在部署图的上下文联系中,关联代表节点间的联系通道。下图显示了一个网络系统的部署图,描述了网络协议为构造型和关联终端的多重性,

作为容器的节点

节点可以包含其他元素,如组件和工件。下图显示了一个嵌入式系统某个部分的部署图。描写了一个被主板节点包含的可执行工件。

组件图

组件图(Component Diagram)描绘了组成一个软件系统的模块和嵌入控件。组件图比类图具有更高层次的抽象-通常运行时一个组件被一个或多个类(或对象)实现。它们象积木那样使得组件能最终构成系统的绝大部分。

上图演示了一些组件和它们的内部关系。装配连接器(Assembly connectors)“连接”由”Product”和”Customer”的提供接口到由 “Order”指定的需求接口。 一个依赖关系映射了客户相关的帐户信息到“Order”需要的 “Payment”需求接口。

实际上,组件图同包图很相似,它们都有明确的界限,把元素分组到逻辑结构中。他们之间的不同是:组件图提供了语义更丰富的分组机制,在组件图中,所有的模型元素都是私有的,而包图只显示公有的成员。

表现组件

组件可表示为带关键字 «component»的矩形类元;也可用右上角有组件图标的矩形表示。

装配连接器

装配连接器在组件 “Component1”的需求接口和另一个组件 “Component2”的提供接口之间建立桥梁; 这个桥梁使得一个组件能提供另一个组件所需要的服务。

带端口组件

使用端口的组件图允许在它的环境指定一个服务和行为,同时这个服务和行为也是组件需要的。当端口进行双向操作的时候,它可以指定输入和输出。下图详述了用于在线服务的带端口组件,它有两个提供接口 “order entry”和 “tracking”,也有 “payment” 需求接口。

包图

包图(Package Diagram)用来表现包和它所包含元素的组织。当用来代表类元素时,包图提供了命名空间的可视化。包图最常用的用途是用来组织用例图和类图,尽管它不局限于这些 UML 元素。

下面是一个包图的例子。

包中的元素共享相同的命名空间,因此,一个指定命名空间的元素必须有唯一的名称。

包可以用来代表物理或逻辑关系。选择把类包括在指定的包里,有助于在同一个包里赋予这些类相同继承层次。通常认为把通过复合相关联的类,以及与它们相协作的类放在同一个包里。

在 UML2.5 中,包用文件夹来表示,包中的元素共享同一个命名空间,并且必须是可识别的,因此要有唯一的名称或类型。包必须显示包名,在附属方框部分有选择的显示包内的元素。

  • 包的合并 - 包之间的合并连接符«merge»定义了源包元素与目标包同名元素之间的泛化关系。源包元素的定义被扩展来包含目标包元素定义。当源包元素与目标包内没有同名元素时,目标包元素的定义不受影响。
  • 包的导入 - 导入连接符 «import»表明目标包的元素,在该例中是一个类 ,在源包中被引用要用非限定修饰名。源包的命名空间获得目标类的接口,目标包的命名空间则不受影响。
  • 嵌套连接符 - 源包和目标包间的嵌套连接符说明目标包完全包含源包。

类图

类图(Class Diagram)展示了面向对象系统的构造模块。描绘了模型或部分模型的静态视图,显示它包含的属性和行为,而不是详细描述操作的功能或完善方法。类图最常用来表达多个类和接口之间的关系。泛化(Generalizations),聚合(aggregations)和关联(associations)分别是类之间继承,复合或应用,及连接的表现。

下面的图显示了类之间的聚合关系。弱聚合(浅色箭头)表现在类 “Account” 使用 “AddressBook”,但是不必要包含它的一个实例。强聚合(图中的黑色箭头)表示了目标类包含源类,例如,”Contact” 和 “ContactGroup”值被包含在 “AddressBook”中。

类(Classes)

类是定义对象所具有的属性和行为的元素。行为用类能理解的合适消息和适合每条消息的操作来描述。 类中也可能定义约束,标记值,构造型。

类的标柱(Class Notation)

类用矩形表示。除类的名称外,还可以选择性地显示属性和操作。 分栏分别用来显示类的名称,属性和操作。

在下面图中,类的类名显示在最上面的分栏,它下面的分栏显示详细属性,如:”center” 属性显示初始化的值。最后面的分栏显示操作,如: setWidth,setLength 和 setPosition 以及他们的参数。 属性和操作名前的标注表示了该属性或操作的可见性: 如果使用 “+”号,这个属性或操作是公共的 ; “-“ 号则代表这个属性或操作是私有的。 “#”号是这个属性或操作被定义为保护的,” ~“ 号代表包的可见性。

接口(Interfaces)

接口是实施者同意满足的行为规范,是一种约定。实现一个接口,类必需支持其要求的行为,使系统按照同样的方式,即公共的接口,处理不相关的元素。

接口有相似于类的外形风格,含有指定的操作,如下图所示。如果没有明确的详细操作,也可以画成一个圆环。当画成圆环的时候,到这个环形标柱的实现连接没有目标箭头。

表(Tables)

表尽管不是基本 UML 的一部分,仍然是“图型”能完成的实例用。在右上角画一个表的小图标来表示。表属性用“图型” «column»表示。 绝大多数表单有一个主键,是由一个或几个字段组成的一个唯一的字码组合加主键操作来访问表格,主键操作“图型”为«PK»。 一些表有一个或多个外键,使用一个或多个字段加一个外键操作,映射到相关表的主键上去,外键操作“图型”为«FK»。

关联(Associations)

关联表明两个模型元素之间有关系,通常用在一个类中被实现为一个实例变量。连接符可以包含两端的命名的角色,基数性,方向和约束。关联是元素之间普通的关系。如果多于两个元素,也可以使用菱形的关联关系。当从类图生成代码时,关联末端的对象将变成目标类中实例变量。见下图示例 “playsFor” 将变成”Player”类中的实例变量。

泛化(Generalizations)

泛化被用来说明继承关系。连接从特定类元到一般类元。泛化的含义是源类继承了目标类的特性。下图的图显示了一个父类泛化一个子类, 类“Circle”的一个实例将会有属性 “ x_position”,“ y_position” , “radius” 和 方法 “display()”。 注意:类 “Shape” 是抽象的,类名显示为斜体。

下图显示了与上图相同信息的视图。

聚合(Aggregations)

聚合通常被用来描述由更小的组件所构成的元素。聚合关系表示为白色菱形箭头指向目标类或父类。

聚合的更强形式 -组合聚合(强聚合) - 显示为黑色菱形箭头,用来组合每次最大化的包含组件。如果一个组合聚合的父类被删除,通常与他相关的所有部分都会被删除,但是,如果一个部件从组合中去掉,将不用删除整个组合。组合是可迁,非对称的关系和递归的。

下面的图示:显示了弱聚合和强聚合的不同。“ address book” 由许多 “contacts” 和 “contact groups”组成。 “contact group” 是一个“contacts”的虚分组; “contact”可以被包含在不止一个 “ contact group”。 如果你删除一个“ address book”,所有的 “contacts” 和 “contact groups” 也将会被删除;如果你删除“ contact group”, 没有 “contacts”会被删除。

关联类(Association Classes)

关联类是一个允许关联连接有属性和操作的构造。下面的示例:显示了远不止简单连接两个类的连接,如给“employee”分配项目。“ employee”在项目中所起的作用是一个复杂的实体,既有自身的也有不属于“employee” 或 “project” 类的细节。 例如,“ employee”可以同时为几个项目工作,有不同的职务头衔和对应的安全权限。

依赖(Dependencies)

依赖被用来描述模型元素间广泛的依赖关系。通常在设计过程早期显示两个元素之间存在某种关系,因为是初期而不能确定具体是什么关系,在设计过程末期,该继承关系会被归入已有构造型 (构造型 可以是实例化 «instantiate»,跟踪 «trace»,导入 «import», 和其它的关系),或被替换成一个更明确类型的连接符。

跟踪(Traces)

跟踪关系是一种特殊化的依赖关系。连接模型元素或跨模型但是具有相同概念的模型元素集。跟踪被经常用来追踪需求和模型的变化。由于变化是双向的,这种依赖关系的顺序通常被忽略。这种关系的属性可以被指定为单向映射,但跟踪是双向的,非正式的和很少可计算的。

实现(Realizations)

是源对象执行或实现目标,实现被用来表达模型的可跟踪性和完整性-业务模型或需求被一个或多个用例实现,用例则被类实现,类被组件实现,等等。这种实现贯穿于系统设计的映射需求和类等,直至抽象建模水平级。从而确保整个系统的一张宏图,它也反映系统的所有微小组成,以及约束和定义它的细节。实现关系用带虚线的实箭头表示。

嵌套(Nestings)

嵌套连接符用来表示源元素嵌套在目标元素中。下图显示“ inner class”的定义,尽管在 EA 中,更多地按照着他们在项目层次视图中的位置来显示这种关系。

复合结构图

复合结构图显示类的内部结构,包括它与系统其他部分的交互点。也显示各部分的配置与关系,这些部分一起执行类元的行为。

类元素已经在类图部分被详细地阐述,这部分用来说明类表现复合元素的方式,如:暴露接口,包含端口和部件。

部件

部件是代表一组(一个或多个)实例的元素,这组实例的拥有者是一类元实例,例如:如果一个图的实例有一组图形元素,则这些图形元素可以被表示为部件,并可以对他们之间的某种关系建模。注意:一个部件可以在它的父类被删除之前从父类中被去掉,这样部件就不会被同时删除了。
部件在类或组件内部显示为不加修饰的方框。

端口

端口是类型化的元素,代表一个包含类元实例的外部可视的部分。端口定义了类元和它的环境之间的交互。端口显示在包含它的部件,类或组合结构的边缘上。端口指定了类元提供的服务,以及类元要求环境提供的服务。
端口显示为所属类元边界指定的方框。

接口

接口与类相似,但是有一些限制,所有的接口操作都是公共和抽象的,不提供任何默认的实现。所有的接口属性都必须是常量。然而,当一个类从一个单独的超级类继承而来,它可以实现多个接口。
当一个接口在图中单列出来,它既可以显示为类元素的方框,带 «interface» 关键字和表明它是抽象的斜体名称,也可以显示为圆环。

注意:圆环标注不显示接口操作。当接口显示为类所有的接口,它们会被当作暴露接口引用。暴露接口可以定义为是提供的,还是需求的。提供接口确认包含它的类元提供指定接口元素定义的操作,可通过类和接口间实现的连接来定义。需求接口说明该类元能与其他类元进行通信,这些类元提供了指定接口元素所定义的操作。需求接口可通过在类和接口间建立依赖连接来定义。
提供接口显示为“带棒球体”,依附在类元边缘。需求接口显示为“带棒杯体”,也是依附在类元边缘。

委托

委托连接器用来定义组件外部端口和接口的内部工作方式。委托连接器表示为带有 «delegate» 关键字的箭头。它连接组件的外部约定,表现为它的端口,到组件部件行为的内部实现。

协作

协作定义了一系列共同协作的角色,它们集体展示一个指定的设计功能。协作图应仅仅显示完成指定任务或功能的角色与属性。隔离主要角色是用来简化结构和澄清行为,也用于重用。一个协作通常实现一个模式。
协作元素显示为椭圆。

角色绑定

角色绑定连接器是一条从连接协作到所要完成该任务类元的连线。它显示为虚线,并在类元端显示作用名。

表现

表现连接器用于连接协作到类元来表示此类元中使用了该协作。显示为带关键字 «represents»的虚线箭头。

发生
发生连接器用于连接协作到类元来表示此协作表现了(同原文)该类元;显示为带关键字«occurrence»的虚线箭头。

对象图

对象图(Object Diagram)可以认为是类图的特殊情形,是类图元素子集,被用来及时强调在某些点,类的实例间的关系。这对理解类图很有帮助。他们在构造上与类图显示没有不同,但是反映出多样性和作用。

类和对象元素

下面的图显示了类元素和对象元素外观上的不同。注意:类元素包括三个部分,分别是名字栏,属性栏和操作栏;对象元素默认为没有分栏。名称显示也有不同:对象名称有下划线,并可能显示该对象实例化所用类元的名称。

运行状态

类元元素可以有任意数量的属性和操作。在对象实例中不会被显示出来。但可能定义对象的运行状态,显示特殊实例的属性设置值。

类和对象图示例

下图是一个对象图,其中插入了类定义图。它例示如何用对象图来测试类图中任务多重性的方法。“car” 类对 “wheel” 类有“1 对多” 的多重性,但是如果已经选择用“1 对 4” 来替代,那样就不会在对象图显示“3 个轮子”的汽车。

参考资料

域名解析协议 DNS

域名系统(英文:Domain Name System,缩写:DNS)是互联网的一项服务。它作为将域名和 IP 地址相互映射的一个分布式数据库,能够使人更方便地访问互联网。DNS 使用 TCP 和 UDP 端口 53。当前,对于每一级域名长度的限制是 63 个字符,域名总长度则不能超过 253 个字符。

关键词:DNS, 域名解析

简介

什么是 DNS

DNS 是一个应用层协议。

域名系统 (DNS) 的作用是将人类可读的域名 (如,www.example.com) 转换为机器可读的 IP 地址 (如,192.0.2.44)。

什么是域名

域名是由一串用点分隔符 . 组成的互联网上某一台计算机或计算机组的名称,用于在数据传输时标识计算机的方位。域名可以说是一个 IP 地址的代称,目的是为了便于记忆后者。例如,wikipedia.org 是一个域名,和 IP 地址 208.80.152.2 相对应。人们可以直接访问 wikipedia.org 来代替 IP 地址,然后域名系统(DNS)就会将它转化成便于机器识别的 IP 地址。这样,人们只需要记忆 wikipedia.org 这一串带有特殊含义的字符,而不需要记忆没有含义的数字。

DNS 的分层

域名系统是分层次的。

在域名系统的层次结构中,各种域名都隶属于域名系统根域的下级。域名的第一级是顶级域,它包括通用顶级域,例如 .com.net.org;以及国家和地区顶级域,例如 .us.cn.tk。顶级域名下一层是二级域名,一级一级地往下。这些域名向人们提供注册服务,人们可以用它创建公开的互联网资源或运行网站。顶级域名的管理服务由对应的域名注册管理机构(域名注册局)负责,注册服务通常由域名注册商负责。

DNS 服务类型

  • 授权型 DNS - 一种授权型 DNS 服务提供一种更新机制,供开发人员用于管理其公用 DNS 名称。然后,它响应 DNS 查询,将域名转换为 IP 地址,以便计算机可以相互通信。授权型 DNS 对域有最终授权且负责提供递归型 DNS 服务器对 IP 地址信息的响应。Amazon Route 53 是一种授权型 DNS 系统。
  • 递归型 DNS - 客户端通常不会对授权型 DNS 服务直接进行查询。而是通常连接到称为解析程序的其他类型 DNS 服务,或递归型 DNS 服务。递归型 DNS 服务就像是旅馆的门童:尽管没有任何自身的 DNS 记录,但是可充当代表您获得 DNS 信息的中间程序。如果递归型 DNS 拥有已缓存或存储一段时间的 DNS 参考,那么它会通过提供源或 IP 信息来响应 DNS 查询。如果没有,则它会将查询传递到一个或多个授权型 DNS 服务器以查找信息。

记录类型

DNS 中,常见的资源记录类型有:

  • NS 记录(域名服务) ─ 指定解析域名或子域名的 DNS 服务器。
  • MX 记录(邮件交换) ─ 指定接收信息的邮件服务器。
  • A 记录(地址) ─ 指定域名对应的 IPv4 地址记录。
  • AAAA 记录(地址) ─ 指定域名对应的 IPv6 地址记录。
  • CNAME(规范) ─ 一个域名映射到另一个域名或 CNAME 记录( example.com 指向 www.example.com )或映射到一个 A记录。
  • PTR 记录(反向记录) ─ PTR 记录用于定义与 IP 地址相关联的名称。 PTR 记录是 A 或 AAAA 记录的逆。 PTR 记录是唯一的,因为它们以 .arpa 根开始并被委派给 IP 地址的所有者。

详细可以参考:维基百科 - 域名服务器记录类型列表

域名解析

主机名到 IP 地址的映射有两种方式:

  • 静态映射 - 在本机上配置域名和 IP 的映射,旨在本机上使用。Windows 和 Linux 的 hosts 文件中的内容就属于静态映射。
  • 动态映射 - 建立一套域名解析系统(DNS),只在专门的 DNS 服务器上配置主机到 IP 地址的映射,网络上需要使用主机名通信的设备,首先需要到 DNS 服务器查询主机所对应的 IP 地址。

通过域名去查询域名服务器,得到 IP 地址的过程叫做域名解析。在解析域名时,一般先静态域名解析,再动态解析域名。可以将一些常用的域名放入静态域名解析表中,这样可以大大提高域名解析效率。

上图展示了一个动态域名解析的流程,步骤如下:

  1. 用户打开 Web 浏览器,在地址栏中输入 www.example.com,然后按 Enter 键。
  2. www.example.com 的请求被路由到 DNS 解析程序,这一般由用户的 Internet 服务提供商 (ISP) 进行管理,例如有线 Internet 服务提供商、DSL 宽带提供商或公司网络。
  3. ISP 的 DNS 解析程序将 www.example.com 的请求转发到 DNS 根名称服务器。
  4. ISP 的 DNS 解析程序再次转发 www.example.com 的请求,这次转发到 .com 域的一个 TLD 名称服务器。.com 域的名称服务器使用与 example.com 域相关的四个 Amazon Route 53 名称服务器的名称来响应该请求。
  5. ISP 的 DNS 解析程序选择一个 Amazon Route 53 名称服务器,并将 www.example.com 的请求转发到该名称服务器。
  6. Amazon Route 53 名称服务器在 example.com 托管区域中查找 www.example.com 记录,获得相关值,例如,Web 服务器的 IP 地址 (192.0.2.44),并将 IP 地址返回至 DNS 解析程序。
  7. ISP 的 DNS 解析程序最终获得用户需要的 IP 地址。解析程序将此值返回至 Web 浏览器。DNS 解析程序还会将 example.com 的 IP 地址缓存 (存储) 您指定的时长,以便它能够在下次有人浏览 example.com 时更快地作出响应。有关更多信息,请参阅存活期 (TTL)。
  8. Web 浏览器将 www.example.com 的请求发送到从 DNS 解析程序中获得的 IP 地址。这是您的内容所处位置,例如,在 Amazon EC2 实例中或配置为网站终端节点的 Amazon S3 存储桶中运行的 Web 服务器。
  9. 192.0.2.44 上的 Web 服务器或其他资源将 www.example.com 的 Web 页面返回到 Web 浏览器,且 Web 浏览器会显示该页面。

🔔 注意:只有配置了域名服务器,才能执行域名解析。

例如,在 Linux 中执行 vim /etc/resolv.conf 命令,在其中添加下面的内容来配置域名服务器地址:

1
nameserver 218.2.135.1

Linux 上的域名相关命令

hostname

hostname 命令用于查看和设置系统的主机名称。环境变量 HOSTNAME 也保存了当前的主机名。在使用 hostname 命令设置主机名后,系统并不会永久保存新的主机名,重新启动机器之后还是原来的主机名。如果需要永久修改主机名,需要同时修改 /etc/hosts/etc/sysconfig/network 的相关内容。

参考:http://man.linuxde.net/hostname

示例:

1
2
$ hostname
AY1307311912260196fcZ

nslookup

nslookup 命令是常用域名查询工具,就是查 DNS 信息用的命令。

参考:http://man.linuxde.net/nslookup

示例:

1
2
3
4
5
6
7
8
[root@localhost ~]# nslookup www.jsdig.com
Server: 202.96.104.15
Address: 202.96.104.15#53

Non-authoritative answer:
www.jsdig.com canonical name = host.1.jsdig.com.
Name: host.1.jsdig.com
Address: 100.42.212.8

更多内容