Dunwu Blog

大道至简,知易行难

网络技术之 CDN

img

简介

CDN 是什么

CDN(Content Delivery Network),即内容分发网络

CDN 是一个全球性的代理服务器分布式网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。它从靠近用户的位置提供内容。通常,HTML/CSS/JS,图片和视频等静态内容由 CDN 提供。CDN 的 DNS 解析会告知客户端连接哪台服务器。CDN 的关键技术主要有内容存储和分发技术。

CDN 的优缺点

  • 优点
    • 访问加速 - 由于 CDN 就近服务,大大降低了网络传播时延,所以自然提高了访问速度。
    • 降低负载 - 如果 CDN 已经能获取数据,那么就不必请求源站,这自然降低了源站(服务器)的负载。
  • 缺点
    • CDN 成本可能因流量而异,可能在权衡之后你将不会使用 CDN。
    • 如果在 TTL 过期之前更新内容,CDN 缓存内容可能会过时。
    • CDN 需要更改静态内容的 URL 地址以指向 CDN。

CDN 原理

CDN 的基本原理是:

  • 广泛采用各种缓存服务器,将这些缓存服务器分布到用户访问相对集中的地区或网络中;
  • 在用户访问网站时,实时地根据网络流量和各节点的连接、负载状况以及到用户的距离和响应时间等综合信息来选择最佳缓存服务器;
  • 然后,将用户的请求重新导向最佳的缓存服务器上,由缓存服务器直接响应用户请求。

CDN 网络架构主要由两大部分,分为中心边缘两部分:

  • 中心指 CDN 网管中心和 DNS 重定向解析中心,负责全局负载均衡,设备系统安装在管理中心机房;
  • 边缘主要指异地节点,CDN 分发的载体,主要由 Cache 和负载均衡器等组成。

CDN 是一个策略性部署的整体系统,包括分布式存储负载均衡内容管理网络请求的重定向4个要件。

分布式存储

CDN 网络将存储资源分布到各个地理位置、各个网段。存储系统作为 CDN 系统密不可分的一部分,将 CDN 分发的文件和数据库表记录内容存储起来,提供持续服务。存储系统采用三级存储架构,包括核心存储、CDN 服务节点分布式缓存和终端本地缓存。任意一个点的存储崩溃或失效,并不影响系统服务的可用性。

img

如 CDN 系统在 5 大运营商(中国电信、中国网通、中国铁通、中国移动、中国联通)以及 2 大专有网络(中国教育和科研计算机网、中国科技网)都布有 CDN 节点。这样就消除了不同运营商之间互联的瓶颈造成的影响,实现了跨运营商的网络加速,保证不同网络中的用户都能得到良好的访问质量。

内容管理

内容管理和全局的网络流量管理(Traffic Management)是 CDN 的核心所在。通过用户就近性和服务器负载的判断,CDN 确保内容以一种极为高效的方式为用户的请求提供服务。总的来说,内容服务基于缓存服务器,也称作**代理缓存(Surrogate)**,它位于网络的边缘,距用户仅有”一跳”(Single Hop)之遥。同时,代理缓存是内容提供商源服务器(通常位于 CDN 服务提供商的数据中心)的一个透明镜像。这样的架构使得 CDN 服务提供商能够代表他们客户,即内容供应商,向最终用户提供尽可能好的体验,而这些用户是不能容忍请求响应时间有任何延迟的。据统计,采用 CDN 技术,能处理整个网站页面的 70%~ 95%的内容访问量,减轻服务器的压力,提升了网站的性能和可扩展性。

负载均衡

CDN 负载均衡系统实现 CDN 的内容路由功能。它的作用是将用户的请求导向整个 CDN 网络中的最佳节点。最佳节点的选定可以根据多种策略,例如距离最近节点负载最轻等。负载均衡系统是整个 CDN 的核心,负载均衡的准确性和效率直接决定了整个 CDN 的效率和性能。通常负载均衡可以分为两个层次:全局负载均衡(GSLB)本地负载均衡(SLB)

网络请求的重定向

当用户访问了使用 CDN 服务的资源时,DNS 域名服务器通过 CNAME 方式将最终域名请求重定向到 CDN 系统中的智能 DNS 负载均衡系统。智能 DNS 负载均衡系统通过一组预先定义好的策略(如内容类型、地理区域、网络负载状况等),将当时能够最快响应用户的节点地址提供给用户,使用户可以得到快速的服务

同时,它还与分布在不同地点的所有 CDN 节点保持通信,搜集各节点的健康状态,确保不将用户的请求分配到任何一个已经不可用的节点上。

参考:

CDN 访问流程

img

  1. 用户在浏览器中访问域名,域名解析的请求被发往网站的 DNS 域名解析服务器;
  2. 由于网站的 DNS 域名解析服务器对此域名的解析设置了 CNAME,请求被指向 CDN 网络中的智能 DNS 负载均衡系统;
  3. 智能 DNS 负载均衡系统对域名进行智能解析,将响应速度最快的节点 IP 返回给用户;浏览器在得到速度最快节点的 IP 地址以后,向 CDN 节点发出访问请求;
  4. 由于是第一次访问,CDN 节点将回到源站取用户请求的数据并发给用户;
  5. 当有其他用户再次访问同样内容时,CDN 将直接将数据返回给客户,完成请求/服务过程。

同时,它还与分布在不同地点的所有 CDN 节点保持通信,搜集各节点的健康状态,确保不将用户的请求分配到任何一个已经不可用的节点上。

参考:

推送和拉取

CDN 服务有推送和拉取两种方式:

CDN 推送

当你服务器上内容发生变动时,推送 CDN 接受新内容。直接推送给 CDN 并重写 URL 地址以指向你的内容的 CDN 地址。你可以配置内容到期时间及何时更新。内容只有在更改或新增是才推送,流量最小化,但储存最大化。

优点在于节省源站带宽,提前将要分发的内容放到 CDN 节点上了,当某个流量高峰来临时,不会把你的源站带宽占满(源站还要留点带宽提供动态 HTML 啊)。

缺点是需要针对 CDN 做接口开发,在被分发内容生成时主动上传给 CDN。

CDN 拉取

CDN 拉取是当第一个用户请求该资源时,从服务器上拉取资源。你将内容留在自己的服务器上并重写 URL 指向 CDN 地址。直到内容被缓存在 CDN 上为止,这样请求只会更慢,

存活时间(TTL)决定缓存多久时间。CDN 拉取方式最小化 CDN 上的储存空间,但如果过期文件并在实际更改之前被拉取,则会导致冗余的流量。

高流量站点使用 CDN 拉取效果不错,因为只有最近请求的内容保存在 CDN 中,流量才能更平衡地分散。

优点在于实现简单。

参考:推送式与拉取式 CDN 服务的优劣问题

资源

Vscode 快速入门

快捷键

命令面板(Command Palette)

根据您当前的上下文访问所有可用命令。

Mac: cmd+shift+p or f1
Windows / Linux: ctrl+shift+p or f1

快速打开文件(Quick Open)

Mac: cmd+p
Windows / Linux: ctrl+p

Status Bar

Mac: shift+cmd+m
Windows / Linux: ctrl+shift+m

改变语言模式

Mac: cmd+k m
Windows / Linux: ctrl+k m

设置

Mac: cmd+,
Windows / Linux: File > Preferences > Settings or ctrl+,

插件

  • Chinese (Simplified) Language Pack for Visual Studio Code
  • Prettier - Code formatter
  • IntelliJ IDEA Keybindings
  • EditorConfig for VS Code
  • Git History

更多内容

Maven 教程之 pom.xml 详解

pom.xml 简介

什么是 pom

POM 是 Project Object Model 的缩写,即项目对象模型。

pom.xml 就是 maven 的配置文件,用以描述项目的各种信息。

pom 配置一览

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
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<!-- The Basics -->
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>...</version>
<packaging>...</packaging>
<dependencies>...</dependencies>
<parent>...</parent>
<dependencyManagement>...</dependencyManagement>
<modules>...</modules>
<properties>...</properties>

<!-- Build Settings -->
<build>...</build>
<reporting>...</reporting>

<!-- More Project Information -->
<name>...</name>
<description>...</description>
<url>...</url>
<inceptionYear>...</inceptionYear>
<licenses>...</licenses>
<organization>...</organization>
<developers>...</developers>
<contributors>...</contributors>

<!-- Environment Settings -->
<issueManagement>...</issueManagement>
<ciManagement>...</ciManagement>
<mailingLists>...</mailingLists>
<scm>...</scm>
<prerequisites>...</prerequisites>
<repositories>...</repositories>
<pluginRepositories>...</pluginRepositories>
<distributionManagement>...</distributionManagement>
<profiles>...</profiles>
</project>

基本配置

  • project - project 是 pom.xml 中描述符的根。
  • modelVersion - modelVersion 指定 pom.xml 符合哪个版本的描述符。maven 2 和 3 只能为 4.0.0。

一般 jar 包被识别为: groupId:artifactId:version 的形式。

1
2
3
4
5
6
7
8
9
10
11
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.codehaus.mojo</groupId>
<artifactId>my-project</artifactId>
<version>1.0</version>
<packaging>war</packaging>
</project>

maven 坐标

在 maven 中,根据 groupIdartifactIdversion 组合成 groupId:artifactId:version 来唯一识别一个 jar 包。

  • groupId - 团体、组织的标识符。团体标识的约定是,它以创建这个项目的组织名称的逆向域名(reverse domain name)开头。一般对应着 java 的包结构。
  • artifactId - 单独项目的唯一标识符。比如我们的 tomcat、commons 等。不要在 artifactId 中包含点号(.)。
  • version - 一个项目的特定版本。
    • maven 有自己的版本规范,一般是如下定义 major version、minor version、incremental version-qualifier ,比如 1.2.3-beta-01。要说明的是,maven 自己判断版本的算法是 major、minor、incremental 部分用数字比较,qualifier 部分用字符串比较,所以要小心 alpha-2 和 alpha-15 的比较关系,最好用 alpha-02 的格式。
    • maven 在版本管理时候可以使用几个特殊的字符串 SNAPSHOT、LATEST、RELEASE。比如 1.0-SNAPSHOT。各个部分的含义和处理逻辑如下说明:
      • SNAPSHOT - 这个版本一般用于开发过程中,表示不稳定的版本。
      • LATEST - 指某个特定构件的最新发布,这个发布可能是一个发布版,也可能是一个 snapshot 版,具体看哪个时间最后。
      • RELEASE :指最后一个发布版。
  • packaging - 项目的类型,描述了项目打包后的输出,默认是 jar。常见的输出类型为:pom, jar, maven-plugin, ejb, war, ear, rar, par。

依赖配置

dependencies

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
...
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-embedder</artifactId>
<version>2.0</version>
<type>jar</type>
<scope>test</scope>
<optional>true</optional>
<exclusions>
<exclusion>
<groupId>org.apache.maven</groupId>
<artifactId>maven-core</artifactId>
</exclusion>
</exclusions>
</dependency>
...
</dependencies>
...
</project>
  • groupId, artifactId, version - 和基本配置中的 groupIdartifactIdversion 意义相同。
  • type - 对应 packaging 的类型,如果不使用 type 标签,maven 默认为 jar。
  • scope - 此元素指的是任务的类路径(编译和运行时,测试等)以及如何限制依赖关系的传递性。有 5 种可用的限定范围:
    • compile - 如果没有指定 scope 标签,maven 默认为这个范围。编译依赖关系在所有 classpath 中都可用。此外,这些依赖关系被传播到依赖项目。
    • provided - 与 compile 类似,但是表示您希望 jdk 或容器在运行时提供它。它只适用于编译和测试 classpath,不可传递。
    • runtime - 此范围表示编译不需要依赖关系,而是用于执行。它是在运行时和测试 classpath,但不是编译 classpath。
    • test - 此范围表示正常使用应用程序不需要依赖关系,仅适用于测试编译和执行阶段。它不是传递的。
    • system - 此范围与 provided 类似,除了您必须提供明确包含它的 jar。该 artifact 始终可用,并且不是在仓库中查找。
  • systemPath - 仅当依赖范围是系统时才使用。否则,如果设置此元素,构建将失败。该路径必须是绝对路径,因此建议使用 propertie 来指定特定的路径,如$ {java.home} / lib。由于假定先前安装了系统范围依赖关系,maven 将不会检查项目的仓库,而是检查库文件是否存在。如果没有,maven 将会失败,并建议您手动下载安装。
  • optional - optional 让其他项目知道,当您使用此项目时,您不需要这种依赖性才能正常工作。
  • exclusions - 包含一个或多个排除元素,每个排除元素都包含一个表示要排除的依赖关系的 groupIdartifactId。与可选项不同,可能或可能不会安装和使用,排除主动从依赖关系树中删除自己。

parent

maven 支持继承功能。子 POM 可以使用 parent 指定父 POM ,然后继承其配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.codehaus.mojo</groupId>
<artifactId>my-parent</artifactId>
<version>2.0</version>
<relativePath>../my-parent</relativePath>
</parent>

<artifactId>my-project</artifactId>
</project>
  • relativePath - 注意 relativePath 元素。在搜索本地和远程存储库之前,它不是必需的,但可以用作 maven 的指示符,以首先搜索给定该项目父级的路径。

dependencyManagement

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

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

dependencyManagement 主要是为了统一管理依赖包的版本,确保所有子项目使用的版本一致,类似的还有pluginspluginManagement

modules

子模块列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.codehaus.mojo</groupId>
<artifactId>my-parent</artifactId>
<version>2.0</version>
<packaging>pom</packaging>

<modules>
<module>my-project</module>
<module>another-project</module>
<module>third-project/pom-example.xml</module>
</modules>
</project>

properties

属性列表。定义的属性可以在 pom.xml 文件中任意处使用。使用方式为 ${propertie}

1
2
3
4
5
6
7
8
9
10
<project>
...
<properties>
<maven.compiler.source>1.7<maven.compiler.source>
<maven.compiler.target>1.7<maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
...
</project>

构建配置

build

build 可以分为 “project build” 和 “profile build”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
...
<!-- "Project Build" contains more elements than just the BaseBuild set -->
<build>...</build>

<profiles>
<profile>
<!-- "Profile Build" contains a subset of "Project Build"s elements -->
<build>...</build>
</profile>
</profiles>
</project>

基本构建配置:

1
2
3
4
5
6
7
8
9
<build>
<defaultGoal>install</defaultGoal>
<directory>${basedir}/target</directory>
<finalName>${artifactId}-${version}</finalName>
<filters>
<filter>filters/filter1.properties</filter>
</filters>
...
</build>

defaultGoal : 默认执行目标或阶段。如果给出了一个目标,它应该被定义为它在命令行中(如 jar:jar)。如果定义了一个阶段(如安装),也是如此。

directory :构建时的输出路径。默认为:${basedir}/target

finalName :这是项目的最终构建名称(不包括文件扩展名,例如:my-project-1.0.jar)

filter :定义 * .properties 文件,其中包含适用于接受其设置的资源的属性列表(如下所述)。换句话说,过滤器文件中定义的“name = value”对在代码中替换$ {name}字符串。

resources

资源的配置。资源文件通常不是代码,不需要编译,而是在项目需要捆绑使用的内容。

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
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<build>
...
<resources>
<resource>
<targetPath>META-INF/plexus</targetPath>
<filtering>false</filtering>
<directory>${basedir}/src/main/plexus</directory>
<includes>
<include>configuration.xml</include>
</includes>
<excludes>
<exclude>**/*.properties</exclude>
</excludes>
</resource>
</resources>
<testResources>
...
</testResources>
...
</build>
</project>
  • resources: 资源元素的列表,每个资源元素描述与此项目关联的文件和何处包含文件。
  • targetPath: 指定从构建中放置资源集的目录结构。目标路径默认为基本目录。将要包装在 jar 中的资源的通常指定的目标路径是 META-INF。
  • filtering: 值为 true 或 false。表示是否要为此资源启用过滤。请注意,该过滤器 * .properties 文件不必定义为进行过滤 - 资源还可以使用默认情况下在 POM 中定义的属性(例如$ {project.version}),并将其传递到命令行中“-D”标志(例如,“-Dname = value”)或由 properties 元素显式定义。过滤文件覆盖上面。
  • directory: 值定义了资源的路径。构建的默认目录是${basedir}/src/main/resources
  • includes: 一组文件匹配模式,指定目录中要包括的文件,使用*作为通配符。
  • excludes: 与 includes 类似,指定目录中要排除的文件,使用*作为通配符。注意:如果 includeexclude 发生冲突,maven 会以 exclude 作为有效项。
  • testResources: testResourcesresources 功能类似,区别仅在于:testResources 指定的资源仅用于 test 阶段,并且其默认资源目录为:${basedir}/src/test/resources

plugins

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<build>
...
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.6</version>
<extensions>false</extensions>
<inherited>true</inherited>
<configuration>
<classifier>test</classifier>
</configuration>
<dependencies>...</dependencies>
<executions>...</executions>
</plugin>
</plugins>
</build>
</project>
  • groupId, artifactId, version :和基本配置中的 groupIdartifactIdversion 意义相同。

  • extensions :值为 true 或 false。是否加载此插件的扩展名。默认为 false。

  • inherited :值为 true 或 false。这个插件配置是否应该适用于继承自这个插件的 POM。默认值为 true。

  • configuration - 这是针对个人插件的配置,这里不扩散讲解。

  • dependencies :这里的 dependencies 是插件本身所需要的依赖。

  • executions :需要记住的是,插件可能有多个目标。每个目标可能有一个单独的配置,甚至可能将插件的目标完全绑定到不同的阶段。执行配置插件的目标的执行。

    • id: 执行目标的标识。
    • goals: 像所有多元化的 POM 元素一样,它包含单个元素的列表。在这种情况下,这个执行块指定的插件目标列表。
    • phase: 这是执行目标列表的阶段。这是一个非常强大的选项,允许将任何目标绑定到构建生命周期中的任何阶段,从而改变 maven 的默认行为。
    • inherited: 像上面的继承元素一样,设置这个 false 会阻止 maven 将这个执行传递给它的子代。此元素仅对父 POM 有意义。
    • configuration: 与上述相同,但将配置限制在此特定目标列表中,而不是插件下的所有目标。
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
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
...
<build>
<plugins>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.1</version>
<executions>
<execution>
<id>echodir</id>
<goals>
<goal>run</goal>
</goals>
<phase>verify</phase>
<inherited>false</inherited>
<configuration>
<tasks>
<echo>Build Dir: ${project.build.directory}</echo>
</tasks>
</configuration>
</execution>
</executions>

</plugin>
</plugins>
</build>
</project>

pluginManagement

dependencyManagement 很相似,在当前 POM 中仅声明插件,而不是实际引入插件。子 POM 中只配置 groupIdartifactId 就可以完成插件的引用,且子 POM 有权重写 pluginManagement 定义。

它的目的在于统一所有子 POM 的插件版本。

directories

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
...
<build>
<sourceDirectory>${basedir}/src/main/java</sourceDirectory>
<scriptSourceDirectory>${basedir}/src/main/scripts</scriptSourceDirectory>
<testSourceDirectory>${basedir}/src/test/java</testSourceDirectory>
<outputDirectory>${basedir}/target/classes</outputDirectory>
<testOutputDirectory>${basedir}/target/test-classes</testOutputDirectory>
...
</build>
</project>

目录元素集合存在于 build 元素中,它为整个 POM 设置了各种目录结构。由于它们在配置文件构建中不存在,所以这些不能由配置文件更改。

如果上述目录元素的值设置为绝对路径(扩展属性时),则使用该目录。否则,它是相对于基础构建目录:${basedir}

extensions

扩展是在此构建中使用的 artifacts 的列表。它们将被包含在运行构建的 classpath 中。它们可以启用对构建过程的扩展(例如为 Wagon 传输机制添加一个 ftp 提供程序),并使活动的插件能够对构建生命周期进行更改。简而言之,扩展是在构建期间激活的 artifacts。扩展不需要实际执行任何操作,也不包含 Mojo。因此,扩展对于指定普通插件接口的多个实现中的一个是非常好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
...
<build>
...
<extensions>
<extension>
<groupId>org.apache.maven.wagon</groupId>
<artifactId>wagon-ftp</artifactId>
<version>1.0-alpha-3</version>
</extension>
</extensions>
...
</build>
</project>

reporting

报告包含特定针对 site 生成阶段的元素。某些 maven 插件可以生成 reporting 元素下配置的报告,例如:生成 javadoc 报告。reportingbuild 元素配置插件的能力相似。明显的区别在于:在执行块中插件目标的控制不是细粒度的,报表通过配置 reportSet 元素来精细控制。而微妙的区别在于 reporting 元素下的 configuration 元素可以用作 build 下的 configuration ,尽管相反的情况并非如此( build 下的 configuration 不影响 reporting 元素下的 configuration )。

另一个区别就是 plugin 下的 outputDirectory 元素。在报告的情况下,默认输出目录为 ${basedir}/target/site

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
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
...
<reporting>
<plugins>
<plugin>
...
<reportSets>
<reportSet>
<id>sunlink</id>
<reports>
<report>javadoc</report>
</reports>
<inherited>true</inherited>
<configuration>
<links>
<link>http://java.sun.com/j2se/1.5.0/docs/api/</link>
</links>
</configuration>
</reportSet>
</reportSets>
</plugin>
</plugins>
</reporting>
...
</project>

项目信息

项目信息相关的这部分标签都不是必要的,也就是说完全可以不填写。

它的作用仅限于描述项目的详细信息。

下面的示例是项目信息相关标签的清单:

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
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
...

<!-- 项目信息 begin -->

<!--项目名-->
<name>maven-notes</name>

<!--项目描述-->
<description>maven 学习笔记</description>

<!--项目url-->
<url>https://github.com/dunwu/maven-notes</url>

<!--项目开发年份-->
<inceptionYear>2017</inceptionYear>

<!--开源协议-->
<licenses>
<license>
<name>Apache License, Version 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
<comments>A business-friendly OSS license</comments>
</license>
</licenses>

<!--组织信息(如公司、开源组织等)-->
<organization>
<name>...</name>
<url>...</url>
</organization>

<!--开发者列表-->
<developers>
<developer>
<id>victor</id>
<name>Zhang Peng</name>
<email>forbreak at 163.com</email>
<url>https://github.com/dunwu</url>
<organization>...</organization>
<organizationUrl>...</organizationUrl>
<roles>
<role>architect</role>
<role>developer</role>
</roles>
<timezone>+8</timezone>
<properties>...</properties>
</developer>
</developers>

<!--代码贡献者列表-->
<contributors>
<contributor>
<!--标签内容和<developer>相同-->
</contributor>
</contributors>

<!-- 项目信息 end -->

...
</project>

这部分标签都非常简单,基本都能做到顾名思义,且都属于可有可无的标签,所以这里仅简单介绍一下:

  • name - 项目完整名称

  • description - 项目描述

  • url - 一般为项目仓库的 host

  • inceptionYear - 开发年份

  • licenses - 开源协议

  • organization - 项目所属组织信息

  • developers - 项目开发者列表

  • contributors - 项目贡献者列表,<contributor> 的子标签和 <developer> 的完全相同。

环境配置

issueManagement

这定义了所使用的缺陷跟踪系统(Bugzilla,TestTrack,ClearQuest 等)。虽然没有什么可以阻止插件使用这些信息的东西,但它主要用于生成项目文档。

1
2
3
4
5
6
7
8
9
10
11
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
...
<issueManagement>
<system>Bugzilla</system>
<url>http://127.0.0.1/bugzilla/</url>
</issueManagement>
...
</project>

ciManagement

CI 构建系统配置,主要是指定通知机制以及被通知的邮箱。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
...
<ciManagement>
<system>continuum</system>
<url>http://127.0.0.1:8080/continuum</url>
<notifiers>
<notifier>
<type>mail</type>
<sendOnError>true</sendOnError>
<sendOnFailure>true</sendOnFailure>
<sendOnSuccess>false</sendOnSuccess>
<sendOnWarning>false</sendOnWarning>
<configuration><address>continuum@127.0.0.1</address></configuration>
</notifier>
</notifiers>
</ciManagement>
...
</project>

mailingLists

邮件列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
...
<mailingLists>
<mailingList>
<name>User List</name>
<subscribe>user-subscribe@127.0.0.1</subscribe>
<unsubscribe>user-unsubscribe@127.0.0.1</unsubscribe>
<post>user@127.0.0.1</post>
<archive>http://127.0.0.1/user/</archive>
<otherArchives>
<otherArchive>http://base.google.com/base/1/127.0.0.1</otherArchive>
</otherArchives>
</mailingList>
</mailingLists>
...
</project>

scm

SCM(软件配置管理,也称为源代码/控制管理或简洁的版本控制)。常见的 scm 有 svn 和 git 。

1
2
3
4
5
6
7
8
9
10
11
12
13
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
...
<scm>
<connection>scm:svn:http://127.0.0.1/svn/my-project</connection>
<developerConnection>scm:svn:https://127.0.0.1/svn/my-project</developerConnection>
<tag>HEAD</tag>
<url>http://127.0.0.1/websvn/my-project</url>
</scm>
...
</project>

prerequisites

POM 执行的预设条件。

1
2
3
4
5
6
7
8
9
10
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
...
<prerequisites>
<maven>2.0.6</maven>
</prerequisites>
...
</project>

repositories

repositories 是遵循 Maven 存储库目录布局的 artifacts 集合。默认的 Maven 中央存储库位于https://repo.maven.apache.org/maven2/上。

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
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
...
<repositories>
<repository>
<releases>
<enabled>false</enabled>
<updatePolicy>always</updatePolicy>
<checksumPolicy>warn</checksumPolicy>
</releases>
<snapshots>
<enabled>true</enabled>
<updatePolicy>never</updatePolicy>
<checksumPolicy>fail</checksumPolicy>
</snapshots>
<id>codehausSnapshots</id>
<name>Codehaus Snapshots</name>
<url>http://snapshots.maven.codehaus.org/maven2</url>
<layout>default</layout>
</repository>
</repositories>
<pluginRepositories>
...
</pluginRepositories>
...
</project>

pluginRepositories

repositories 差不多。

1
2
3
4
5
6
7
8
9
10
11
12
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
...
<distributionManagement>
...
<downloadUrl>http://mojo.codehaus.org/my-project</downloadUrl>
<status>deployed</status>
</distributionManagement>
...
</project>

distributionManagement

它管理在整个构建过程中生成的 artifact 和支持文件的分布。从最后的元素开始:

1
2
3
4
5
6
7
8
9
10
11
12
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
...
<distributionManagement>
...
<downloadUrl>http://mojo.codehaus.org/my-project</downloadUrl>
<status>deployed</status>
</distributionManagement>
...
</project>
  • repository - 与 repositories 相似

  • site - 站点信息

  • relocation - 项目迁移位置

profiles

activation 是一个 profile 的关键。配置文件的功能来自于在某些情况下仅修改基本 POM 的功能。这些情况通过 activation 元素指定。

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
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
...
<profiles>
<profile>
<id>test</id>
<activation>
<activeByDefault>false</activeByDefault>
<jdk>1.5</jdk>
<os>
<name>Windows XP</name>
<family>Windows</family>
<arch>x86</arch>
<version>5.1.2600</version>
</os>
<property>
<name>sparrow-type</name>
<value>African</value>
</property>
<file>
<exists>${basedir}/file2.properties</exists>
<missing>${basedir}/file1.properties</missing>
</file>
</activation>
...
</profile>
</profiles>
</project>

参考资料

Maven 教程之 settings.xml 详解

settings.xml 简介

settings.xml 有什么用

从 settings.xml 的文件名就可以看出,它是用来设置 maven 参数的配置文件。settings.xml 中包含类似本地仓储位置、修改远程仓储服务器、认证信息等配置。

  • settings.xml 是 maven 的全局配置文件
  • pom.xml 文件是本地项目配置文件

settings.xml 文件位置

settings.xml 文件一般存在于两个位置:

  • 全局配置 - ${maven.home}/conf/settings.xml
  • 用户配置 - ${user.home}/.m2/settings.xml

🔔 注意:用户配置优先于全局配置。${user.home} 和和所有其他系统属性只能在 3.0+版本上使用。请注意 windows 和 Linux 使用变量的区别。

配置优先级

重要:局部配置优先于全局配置

配置优先级从高到低:pom.xml > user settings > global settings

如果这些文件同时存在,在应用配置时,会合并它们的内容,如果有重复的配置,优先级高的配置会覆盖优先级低的。

settings.xml 元素详解

顶级元素概览

下面列举了settings.xml中的顶级元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
https://maven.apache.org/xsd/settings-1.0.0.xsd">
<localRepository/>
<interactiveMode/>
<usePluginRegistry/>
<offline/>
<pluginGroups/>
<servers/>
<mirrors/>
<proxies/>
<profiles/>
<activeProfiles/>
</settings>

LocalRepository

作用:该值表示构建系统本地仓库的路径。

其默认值:~/.m2/repository。

1
<localRepository>${user.home}/.m2/repository</localRepository>

InteractiveMode

作用:表示 maven 是否需要和用户交互以获得输入。

如果 maven 需要和用户交互以获得输入,则设置成 true,反之则应为 false。默认为 true。

1
<interactiveMode>true</interactiveMode>

UsePluginRegistry

作用:maven 是否需要使用 plugin-registry.xml 文件来管理插件版本。

如果需要让 maven 使用文件~/.m2/plugin-registry.xml 来管理插件版本,则设为 true。默认为 false。

1
<usePluginRegistry>false</usePluginRegistry>

Offline

作用:表示 maven 是否需要在离线模式下运行。

如果构建系统需要在离线模式下运行,则为 true,默认为 false。

当由于网络设置原因或者安全因素,构建服务器不能连接远程仓库的时候,该配置就十分有用。

1
<offline>false</offline>

PluginGroups

作用:当插件的组织 id(groupId)没有显式提供时,供搜寻插件组织 Id(groupId)的列表。

该元素包含一个 pluginGroup 元素列表,每个子元素包含了一个组织 Id(groupId)。

当我们使用某个插件,并且没有在命令行为其提供组织 Id(groupId)的时候,Maven 就会使用该列表。默认情况下该列表包含了 org.apache.maven.pluginsorg.codehaus.mojo

1
2
3
4
5
6
7
8
9
10
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
https://maven.apache.org/xsd/settings-1.0.0.xsd">
...
<pluginGroups>
<!--plugin的组织Id(groupId) -->
<pluginGroup>org.codehaus.mojo</pluginGroup>
</pluginGroups>
...
</settings>

Servers

作用:一般,仓库的下载和部署是在 pom.xml 文件中的 repositoriesdistributionManagement 元素中定义的。然而,一般类似用户名、密码(有些仓库访问是需要安全认证的)等信息不应该在 pom.xml 文件中配置,这些信息可以配置在 settings.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
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
https://maven.apache.org/xsd/settings-1.0.0.xsd">
...
<!--配置服务端的一些设置。一些设置如安全证书不应该和pom.xml一起分发。这种类型的信息应该存在于构建服务器上的settings.xml文件中。 -->
<servers>
<!--服务器元素包含配置服务器时需要的信息 -->
<server>
<!--这是server的id(注意不是用户登陆的id),该id与distributionManagement中repository元素的id相匹配。 -->
<id>server001</id>
<!--鉴权用户名。鉴权用户名和鉴权密码表示服务器认证所需要的登录名和密码。 -->
<username>my_login</username>
<!--鉴权密码 。鉴权用户名和鉴权密码表示服务器认证所需要的登录名和密码。密码加密功能已被添加到2.1.0 +。详情请访问密码加密页面 -->
<password>my_password</password>
<!--鉴权时使用的私钥位置。和前两个元素类似,私钥位置和私钥密码指定了一个私钥的路径(默认是${user.home}/.ssh/id_dsa)以及如果需要的话,一个密语。将来passphrase和password元素可能会被提取到外部,但目前它们必须在settings.xml文件以纯文本的形式声明。 -->
<privateKey>${usr.home}/.ssh/id_dsa</privateKey>
<!--鉴权时使用的私钥密码。 -->
<passphrase>some_passphrase</passphrase>
<!--文件被创建时的权限。如果在部署的时候会创建一个仓库文件或者目录,这时候就可以使用权限(permission)。这两个元素合法的值是一个三位数字,其对应了unix文件系统的权限,如664,或者775。 -->
<filePermissions>664</filePermissions>
<!--目录被创建时的权限。 -->
<directoryPermissions>775</directoryPermissions>
</server>
</servers>
...
</settings>

Mirrors

作用:为仓库列表配置的下载镜像列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
https://maven.apache.org/xsd/settings-1.0.0.xsd">
...
<mirrors>
<!-- 给定仓库的下载镜像。 -->
<mirror>
<!-- 该镜像的唯一标识符。id用来区分不同的mirror元素。 -->
<id>planetmirror.com</id>
<!-- 镜像名称 -->
<name>PlanetMirror Australia</name>
<!-- 该镜像的URL。构建系统会优先考虑使用该URL,而非使用默认的服务器URL。 -->
<url>http://downloads.planetmirror.com/pub/maven2</url>
<!-- 被镜像的服务器的id。例如,如果我们要设置了一个Maven中央仓库(http://repo.maven.apache.org/maven2/)的镜像,就需要将该元素设置成central。这必须和中央仓库的id central完全一致。 -->
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>
...
</settings>

Proxies

作用:用来配置不同的代理。

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
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
https://maven.apache.org/xsd/settings-1.0.0.xsd">
...
<proxies>
<!--代理元素包含配置代理时需要的信息 -->
<proxy>
<!--代理的唯一定义符,用来区分不同的代理元素。 -->
<id>myproxy</id>
<!--该代理是否是激活的那个。true则激活代理。当我们声明了一组代理,而某个时候只需要激活一个代理的时候,该元素就可以派上用处。 -->
<active>true</active>
<!--代理的协议。 协议://主机名:端口,分隔成离散的元素以方便配置。 -->
<protocol>http</protocol>
<!--代理的主机名。协议://主机名:端口,分隔成离散的元素以方便配置。 -->
<host>proxy.somewhere.com</host>
<!--代理的端口。协议://主机名:端口,分隔成离散的元素以方便配置。 -->
<port>8080</port>
<!--代理的用户名,用户名和密码表示代理服务器认证的登录名和密码。 -->
<username>proxyuser</username>
<!--代理的密码,用户名和密码表示代理服务器认证的登录名和密码。 -->
<password>somepassword</password>
<!--不该被代理的主机名列表。该列表的分隔符由代理服务器指定;例子中使用了竖线分隔符,使用逗号分隔也很常见。 -->
<nonProxyHosts>*.google.com|ibiblio.org</nonProxyHosts>
</proxy>
</proxies>
...
</settings>

Profiles

作用:根据环境参数来调整构建配置的列表。

settings.xml 中的 profile 元素是 pom.xmlprofile 元素的裁剪版本

它包含了idactivationrepositoriespluginRepositoriesproperties 元素。这里的 profile 元素只包含这五个子元素是因为这里只关心构建系统这个整体(这正是 settings.xml 文件的角色定位),而非单独的项目对象模型设置。如果一个 settings.xml 中的 profile 被激活,它的值会覆盖任何其它定义在 pom.xml 中带有相同 id 的 profile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
https://maven.apache.org/xsd/settings-1.0.0.xsd">
...
<profiles>
<profile>
<!-- profile的唯一标识 -->
<id>test</id>
<!-- 自动触发profile的条件逻辑 -->
<activation />
<!-- 扩展属性列表 -->
<properties />
<!-- 远程仓库列表 -->
<repositories />
<!-- 插件仓库列表 -->
<pluginRepositories />
</profile>
</profiles>
...
</settings>

Activation

作用:自动触发 profile 的条件逻辑。

pom.xml 中的 profile 一样,profile 的作用在于它能够在某些特定的环境中自动使用某些特定的值;这些环境通过 activation 元素指定。
activation 元素并不是激活 profile 的唯一方式。settings.xml 文件中的 activeProfile 元素可以包含 profileidprofile 也可以通过在命令行,使用 -P 标记和逗号分隔的列表来显式的激活(如,-P test)。

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
<activation>
<!--profile默认是否激活的标识 -->
<activeByDefault>false</activeByDefault>
<!--当匹配的jdk被检测到,profile被激活。例如,1.4激活JDK1.4,1.4.0_2,而!1.4激活所有版本不是以1.4开头的JDK。 -->
<jdk>1.5</jdk>
<!--当匹配的操作系统属性被检测到,profile被激活。os元素可以定义一些操作系统相关的属性。 -->
<os>
<!--激活profile的操作系统的名字 -->
<name>Windows XP</name>
<!--激活profile的操作系统所属家族(如 'windows') -->
<family>Windows</family>
<!--激活profile的操作系统体系结构 -->
<arch>x86</arch>
<!--激活profile的操作系统版本 -->
<version>5.1.2600</version>
</os>
<!--如果Maven检测到某一个属性(其值可以在POM中通过${name}引用),其拥有对应的name = 值,Profile就会被激活。如果值字段是空的,那么存在属性名称字段就会激活profile,否则按区分大小写方式匹配属性值字段 -->
<property>
<!--激活profile的属性的名称 -->
<name>mavenVersion</name>
<!--激活profile的属性的值 -->
<value>2.0.3</value>
</property>
<!--提供一个文件名,通过检测该文件的存在或不存在来激活profile。missing检查文件是否存在,如果不存在则激活profile。另一方面,exists则会检查文件是否存在,如果存在则激活profile。 -->
<file>
<!--如果指定的文件存在,则激活profile。 -->
<exists>${basedir}/file2.properties</exists>
<!--如果指定的文件不存在,则激活profile。 -->
<missing>${basedir}/file1.properties</missing>
</file>
</activation>

注:在 maven 工程的 pom.xml 所在目录下执行 mvn help:active-profiles 命令可以查看中央仓储的 profile 是否在工程中生效。

properties

作用:对应profile的扩展属性列表。

maven 属性和 ant 中的属性一样,可以用来存放一些值。这些值可以在 pom.xml 中的任何地方使用标记${X}来使用,这里 X 是指属性的名称。属性有五种不同的形式,并且都能在 settings.xml 文件中访问。

1
2
3
4
5
6
7
8
9
10
<!--
1. env.X: 在一个变量前加上"env."的前缀,会返回一个shell环境变量。例如,"env.PATH"指代了$path环境变量(在Windows上是%PATH%)。
2. project.x:指代了POM中对应的元素值。例如: <project><version>1.0</version></project>通过${project.version}获得version的值。
3. settings.x: 指代了settings.xml中对应元素的值。例如:<settings><offline>false</offline></settings>通过 ${settings.offline}获得offline的值。
4. Java System Properties: 所有可通过java.lang.System.getProperties()访问的属性都能在POM中使用该形式访问,例如 ${java.home}。
5. x: 在<properties/>元素中,或者外部文件中设置,以${someVar}的形式使用。
-->
<properties>
<user.install>${user.home}/our-project</user.install>
</properties>

注:如果该 profile 被激活,则可以在pom.xml中使用${user.install}。

Repositories

作用:远程仓库列表,它是 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
<repositories>
<!--包含需要连接到远程仓库的信息 -->
<repository>
<!--远程仓库唯一标识 -->
<id>codehausSnapshots</id>
<!--远程仓库名称 -->
<name>Codehaus Snapshots</name>
<!--如何处理远程仓库里发布版本的下载 -->
<releases>
<!--true或者false表示该仓库是否为下载某种类型构件(发布版,快照版)开启。 -->
<enabled>false</enabled>
<!--该元素指定更新发生的频率。Maven会比较本地POM和远程POM的时间戳。这里的选项是:always(一直),daily(默认,每日),interval:X(这里X是以分钟为单位的时间间隔),或者never(从不)。 -->
<updatePolicy>always</updatePolicy>
<!--当Maven验证构件校验文件失败时该怎么做-ignore(忽略),fail(失败),或者warn(警告)。 -->
<checksumPolicy>warn</checksumPolicy>
</releases>
<!--如何处理远程仓库里快照版本的下载。有了releases和snapshots这两组配置,POM就可以在每个单独的仓库中,为每种类型的构件采取不同的策略。例如,可能有人会决定只为开发目的开启对快照版本下载的支持。参见repositories/repository/releases元素 -->
<snapshots>
<enabled />
<updatePolicy />
<checksumPolicy />
</snapshots>
<!--远程仓库URL,按protocol://hostname/path形式 -->
<url>http://snapshots.maven.codehaus.org/maven2</url>
<!--用于定位和排序构件的仓库布局类型-可以是default(默认)或者legacy(遗留)。Maven 2为其仓库提供了一个默认的布局;然而,Maven 1.x有一种不同的布局。我们可以使用该元素指定布局是default(默认)还是legacy(遗留)。 -->
<layout>default</layout>
</repository>
</repositories>

pluginRepositories

作用:发现插件的远程仓库列表。

repository 类似,只是 repository 是管理 jar 包依赖的仓库,pluginRepositories 则是管理插件的仓库。
maven 插件是一种特殊类型的构件。由于这个原因,插件仓库独立于其它仓库。pluginRepositories 元素的结构和 repositories 元素的结构类似。每个 pluginRepository 元素指定一个 Maven 可以用来寻找新插件的远程地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<pluginRepositories>
<!-- 包含需要连接到远程插件仓库的信息.参见profiles/profile/repositories/repository元素的说明 -->
<pluginRepository>
<releases>
<enabled />
<updatePolicy />
<checksumPolicy />
</releases>
<snapshots>
<enabled />
<updatePolicy />
<checksumPolicy />
</snapshots>
<id />
<name />
<url />
<layout />
</pluginRepository>
</pluginRepositories>

ActiveProfiles

作用:手动激活 profiles 的列表,按照profile被应用的顺序定义activeProfile

该元素包含了一组 activeProfile 元素,每个 activeProfile 都含有一个 profile id。任何在 activeProfile 中定义的 profile id,不论环境设置如何,其对应的 profile 都会被激活。如果没有匹配的 profile,则什么都不会发生。

例如,env-test 是一个 activeProfile,则在 pom.xml(或者 profile.xml)中对应 id 的 profile 会被激活。如果运行过程中找不到这样一个 profile,Maven 则会像往常一样运行。

1
2
3
4
5
6
7
8
9
10
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
https://maven.apache.org/xsd/settings-1.0.0.xsd">
...
<activeProfiles>
<!-- 要激活的profile id -->
<activeProfile>env-test</activeProfile>
</activeProfiles>
...
</settings>

至此,maven settings.xml 中的标签都讲解完毕,希望对大家有所帮助。

参考资料

Maven 教程之发布 jar 到私服或中央仓库

发布 jar 包到中央仓库

为了避免重复造轮子,相信每个 Java 程序员都想打造自己的脚手架或工具包(自己定制的往往才是最适合自己的)。那么如何将自己的脚手架发布到中央仓库呢?下面我们将一步步来实现。

在 Sonatype 创建 Issue

(1)注册 Sonatype 账号

发布 Java 包到 Maven 中央仓库,首先需要在 Sonatype 网站创建一个工单(Issues)。

第一次使用这个网站的时候需要注册自己的帐号(这个帐号和密码需要记住,后面会用到)。

(2)创建 Issue

注册账号成功后,根据你 Java 包的功能分别写上SummaryDescriptionGroup IdSCM url以及Project URL等必要信息,可以参见我之前创建的 Issue:OSSRH-36187

img

创建完之后需要等待 Sonatype 的工作人员审核处理,审核时间还是很快的,我的审核差不多等待了两小时。当 Issue 的 Status 变为RESOLVED后,就可以进行下一步操作了。

说明:如果你的 Group Id 填写的是自己的网站(我的就是这种情况),Sonatype 的工作人员会询问你那个 Group Id 是不是你的域名,你只需要在上面回答是就行,然后就会通过审核。

使用 GPG 生成公私钥对

(1)安装 Gpg4win

Windows 系统,可以下载 Gpg4win 软件来生成密钥对。

Gpg4win 下载地址

安装后,执行命令 gpg –version 检查是否安装成功。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
C:\Program Files (x86)\GnuPG\bin>gpg --version
gpg (GnuPG) 2.2.10
libgcrypt 1.8.3
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the exdunwu permitted by law.

Home: C:/Users/Administrator/AppData/Roaming/gnupg
Supported algorithms:
Pubkey: RSA, ELG, DSA, ECDH, ECDSA, EDDSA
Cipher: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, TWOFISH,
CAMELLIA128, CAMELLIA192, CAMELLIA256
Hash: SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224
Compression: Uncompressed, ZIP, ZLIB, BZIP2

(2)生成密钥对

执行命令 gpg --gen-key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
C:\Program Files (x86)\GnuPG\bin>gpg --gen-key
gpg (GnuPG) 2.2.10; Copyright (C) 2018 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the exdunwu permitted by law.

Note: Use "gpg --full-generate-key" for a full featured key generation dialog.

GnuPG needs to construct a user ID to identify your key.

Real name: Zhang Peng
Email address: forbreak@163.com
You selected this USER-ID:
"Zhang Peng <forbreak@163.com>"

Change (N)ame, (E)mail, or (O)kay/(Q)uit? O

说明:按照提示,依次输入用户名、邮箱。

(3)查看公钥

1
2
3
4
5
6
7
8
9
10
11
12
C:\Program Files (x86)\GnuPG\bin>gpg --list-keys

gpg: checking the trustdb
gpg: marginals needed: 3 completes needed: 1 trust model: pgp
gpg: depth: 0 valid: 2 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 2u
gpg: next trustdb check due at 2020-11-05
C:/Users/Administrator/AppData/Roaming/gnupg/pubring.kbx
--------------------------------------------------------
pub rsa2048 2018-11-06 [SC] [expires: 2020-11-06]
E4CE537A3803D49C35332221A306519BAFF57F60
uid [ultimate] forbreak <forbreak@163.com>
sub rsa2048 2018-11-06 [E] [expires: 2020-11-06]

说明:其中,E4CE537A3803D49C35332221A306519BAFF57F60 就是公钥

(4)将公钥发布到 PGP 密钥服务器

执行 gpg --keyserver hkp://pool.sks-keyservers.net --send-keys 发布公钥:

1
2
C:\Program Files (x86)\GnuPG\bin>gpg --keyserver hkp://pool.sks-keyservers.net --send-keys E4CE537A3803D49C35332221A306519BAFF57F60
gpg: sending key A306519BAFF57F60 to hkp://pool.sks-keyservers.net

🔔 注意:有可能出现 gpg: keyserver receive failed: No dat 错误,等大约 30 分钟后再执行就不会报错了。

(5)查看公钥是否发布成功

执行 gpg --keyserver hkp://pool.sks-keyservers.net --recv-keys 查看公钥是否发布成功。

1
2
3
4
C:\Program Files (x86)\GnuPG\bin>gpg --keyserver hkp://pool.sks-keyservers.net --recv-keys E4CE537A3803D49C35332221A306519BAFF57F60
gpg: key A306519BAFF57F60: "forbreak <forbreak@163.com>" not changed
gpg: Total number processed: 1
gpg: unchanged: 1

Maven 配置

完成了前两个章节的准备工作,就可以将 jar 包上传到中央仓库了。当然了,我们还要对 maven 做一些配置。

settings.xml 配置

一份完整的 settings.xml 配置示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?xml version="1.0" encoding="UTF-8"?>

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">

<pluginGroups>
<pluginGroup>org.sonatype.plugins</pluginGroup>
</pluginGroups>

<!-- 用户名、密码就是 Sonatype 账号、密码 -->
<servers>
<server>
<id>sonatype-snapshots</id>
<username>xxxxxx</username>
<password>xxxxxx</password>
</server>
<server>
<id>sonatype-staging</id>
<username>xxxxxx</username>
<password>xxxxxx</password>
</server>
</servers>

<!-- 使用 aliyun maven 仓库加速下载 -->
<mirrors>
<mirror>
<id>nexus-aliyun</id>
<mirrorOf>*</mirrorOf>
<name>Aliyun</name>
<url>http://maven.aliyun.com/nexus/groups/public</url>
</mirror>
</mirrors>

<!-- gpg 的密码,注意,这里不是指公钥 -->
<profiles>
<profile>
<id>sonatype</id>
<properties>
<gpg.executable>C:/Program Files (x86)/GnuPG/bin/gpg.exe</gpg.executable>
<gpg.passphrase>xxxxxx</gpg.passphrase>
</properties>
</profile>
</profiles>

<activeProfiles>
<activeProfile>sonatype</activeProfile>
</activeProfiles>
</settings>

pom.xml 配置

(1)添加 licenses、scm、developers 配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>

<developers>
<developer>
<name>xxxxxx</name>
<email>forbreak@163.com</email>
<url>https://github.com/dunwu</url>
</developer>
</developers>

<scm>
<url>https://github.com/dunwu/dunwu</url>
<connection>git@github.com:dunwu/dunwu.git</connection>
<developerConnection>https://github.com/dunwu</developerConnection>
</scm>

(2)添加 distributionManagement 配置

1
2
3
4
5
6
7
8
9
10
<distributionManagement>
<snapshotRepository>
<id>sonatype-snapshots</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</snapshotRepository>
<repository>
<id>sonatype-staging</id>
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2</url>
</repository>
</distributionManagement>

说明:<snapshotRepository> 指定的是 snapshot 仓库地址;<repository> 指定的是 staging (正式版)仓库地址。需要留意的是,这里的 id 需要和 settings.xml 中的 <server> 的 id 保持一致。

(3)添加 profiles 配置

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
 <profiles>
<profile>
<id>sonatype</id>
<build>
<plugins>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.7</version>
<extensions>true</extensions>
<configuration>
<serverId>sonatype-snapshots</serverId>
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
<autoReleaseAfterClose>true</autoReleaseAfterClose>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.0.1</version>
<configuration>
<failOnError>false</failOnError>
<quiet>true</quiet>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>

部署和发布

按照上面的步骤配置完后,一切都已经 OK。

此时,使用 mvn clean deploy -P sonatype 命令就可以发布 jar 包到中央仓库了:

说明:-P 参数后面的 sonatype 需要和 pom.xml 中 <profile> 的 id 保持一致,才能激活 profile。

部署 maven 私服

工作中,Java 程序员开发的商用 Java 项目,一般不想发布到中央仓库,使得人人尽知。这时,我们就需要搭建私服,将 maven 服务器部署在公司内部网络,从而避免 jar 包流传出去。怎么做呢,让我们来一步步学习吧。

下载安装 Nexus

进入官方下载地址,选择合适版本下载。

img

本人希望将 Nexus 部署在 Linux 机器,所以选用的是 Unix 版本。

这里,如果想通过命令方式直接下载(比如用脚本安装),可以在官方历史发布版本页面中找到合适版本,然后执行以下命令:

1
2
wget -O /opt/maven/nexus-unix.tar.gz http://download.sonatype.com/nexus/3/nexus-3.13.0-01-unix.tar.gz
tar -zxf nexus-unix.tar.gz

解压后,有两个目录:

  • nexus-3.13.0-01 - 包含了 Nexus 运行所需要的文件。是 Nexus 运行必须的。
  • sonatype-work - 包含了 Nexus 生成的配置文件、日志文件、仓库文件等。当我们需要备份 Nexus 的时候默认备份此目录即可。

启动停止 Nexus

进入 nexus-3.13.0-01/bin 目录,有一个可执行脚本 nexus。

执行 ./nexus,可以查看允许执行的参数,如下所示,含义可谓一目了然:

1
2
$ ./nexus
Usage: ./nexus {start|stop|run|run-redirect|status|restart|force-reload}
  • 启动 nexus - ./nexus start
  • 停止 nexus -

启动成功后,在浏览器中访问 http://<ip>:8081,欢迎页面如下图所示:

img

点击右上角 Sign in 登录,默认用户名/密码为:admin/admin123。

有必要提一下的是,在 Nexus 的 Repositories 管理页面,展示了可用的 maven 仓库,如下图所示:

img

说明:

  • maven-central - maven 中央库(如果没有配置 mirror,默认就从这里下载 jar 包),从 https://repo1.maven.org/maven2/ 获取资源
  • maven-releases - 存储私有仓库的发行版 jar 包
  • maven-snapshots - 存储私有仓库的快照版(调试版本) jar 包
  • maven-public - 私有仓库的公共空间,把上面三个仓库组合在一起对外提供服务,在本地 maven 基础配置 settings.xml 中使用。

使用 Nexus

如果要使用 Nexus,还必须在 settings.xml 和 pom.xml 中配置认证信息。

配置 settings.xml

一份完整的 settings.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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
<?xml version="1.0" encoding="UTF-8"?>

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<pluginGroups>
<pluginGroup>org.sonatype.plugins</pluginGroup>
</pluginGroups>

<!-- Maven 私服账号信息 -->
<servers>
<server>
<id>releases</id>
<username>admin</username>
<password>admin123</password>
</server>
<server>
<id>snapshots</id>
<username>admin</username>
<password>admin123</password>
</server>
</servers>

<!-- jar 包下载地址 -->
<mirrors>
<mirror>
<id>public</id>
<mirrorOf>*</mirrorOf>
<url>http://10.255.255.224:8081/repository/maven-public/</url>
</mirror>
</mirrors>

<profiles>
<profile>
<id>zp</id>
<repositories>
<repository>
<id>central</id>
<url>http://central</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>central</id>
<url>http://central</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>

<activeProfiles>
<activeProfile>zp</activeProfile>
</activeProfiles>
</settings>

配置 pom.xml

在 pom.xml 中添加如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
<distributionManagement>
<repository>
<id>releases</id>
<name>Releases</name>
<url>http://10.255.255.224:8081/repository/maven-releases</url>
</repository>
<snapshotRepository>
<id>snapshots</id>
<name>Snapshot</name>
<url>http://10.255.255.224:8081/repository/maven-snapshots</url>
</snapshotRepository>
</distributionManagement>

🔔 注意:

  • <repository><snapshotRepository> 的 id 必须和 settings.xml 配置文件中的 <server> 标签中的 id 匹配。
  • <url> 标签的地址需要和 maven 私服的地址匹配。

执行 maven 构建

如果要使用 settings.xml 中的私服配置,必须通过指定 -P zp 来激活 profile。

示例:

1
2
3
4
5
## 编译并打包 maven 项目
$ mvn clean package -Dmaven.skip.test=true -P zp

## 编译并上传 maven 交付件(jar 包)
$ mvn clean deploy -Dmaven.skip.test=true -P zp

参考资料

深入理解 Java 序列化

关键词:SerializableserialVersionUIDtransientExternalizablewriteObjectreadObject

img

序列化简介

由于,网络传输的数据必须是二进制数据,但调用方请求的出入参数都是对象。对象是不能直接在网络中传输的,所以需要提前把它转成可传输的二进制,并且要求转换算法是可逆的。

  • 序列化(serialize):序列化是将对象转换为二进制数据。
  • 反序列化(deserialize):反序列化是将二进制数据转换为对象。

img

序列化用途

  • 序列化可以将对象的字节序列持久化——保存在内存、文件、数据库中。
  • 在网络上传送对象的字节序列。
  • RMI(远程方法调用)

JDK 序列化

JDK 中内置了一种序列化方式。

ObjectInputStream 和 ObjectOutputStream

Java 通过对象输入输出流来实现序列化和反序列化:

  • java.io.ObjectOutputStream 类的 writeObject() 方法可以实现序列化;
  • java.io.ObjectInputStream 类的 readObject() 方法用于实现反序列化。

序列化和反序列化示例:

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
public class SerializeDemo01 {
enum Sex {
MALE,
FEMALE
}


static class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name = null;
private Integer age = null;
private Sex sex;

public Person() { }

public Person(String name, Integer age, Sex sex) {
this.name = name;
this.age = age;
this.sex = sex;
}

@Override
public String toString() {
return "Person{" + "name='" + name + '\'' + ", age=" + age + ", sex=" + sex + '}';
}
}

/**
* 序列化
*/
private static void serialize(String filename) throws IOException {
File f = new File(filename); // 定义保存路径
OutputStream out = new FileOutputStream(f); // 文件输出流
ObjectOutputStream oos = new ObjectOutputStream(out); // 对象输出流
oos.writeObject(new Person("Jack", 30, Sex.MALE)); // 保存对象
oos.close();
out.close();
}

/**
* 反序列化
*/
private static void deserialize(String filename) throws IOException, ClassNotFoundException {
File f = new File(filename); // 定义保存路径
InputStream in = new FileInputStream(f); // 文件输入流
ObjectInputStream ois = new ObjectInputStream(in); // 对象输入流
Object obj = ois.readObject(); // 读取对象
ois.close();
in.close();
System.out.println(obj);
}

public static void main(String[] args) throws IOException, ClassNotFoundException {
final String filename = "d:/text.dat";
serialize(filename);
deserialize(filename);
}
}
// Output:
// Person{name='Jack', age=30, sex=MALE}

JDK 的序列化过程是怎样完成的呢?

img

图来自 RPC 实战与核心原理 - 序列化

序列化过程就是在读取对象数据的时候,不断加入一些特殊分隔符,这些特殊分隔符用于在反序列化过程中截断用,这就像是文章中的标点符号被用于断句一样。

  • 头部数据用来声明序列化协议、序列化版本,用于高低版本向后兼容。
  • 对象数据主要包括类名、签名、属性名、属性类型及属性值,当然还有开头结尾等数据,除了属性值属于真正的对象值,其他都是为了反序列化用的元数据。
  • 存在对象引用、继承的情况下,就是递归遍历“写对象”逻辑。

🔔 注意:使用 Java 对象序列化,在保存对象时,会把其状态保存为一组字节,在未来,再将这些字节组装成对象。必须注意地是,对象序列化保存的是对象的”状态”,即它的成员变量。由此可知,对象序列化不会关注类中的静态变量

Serializable 接口

被序列化的类必须属于 EnumArraySerializable 类型其中的任何一种,否则将抛出 NotSerializableException 异常。这是因为:在序列化操作过程中会对类型进行检查,如果不满足序列化类型要求,就会抛出异常。

【示例】NotSerializableException 错误

1
2
3
4
public class UnSerializeDemo {
static class Person { // 其他内容略 }
// 其他内容略
}

输出:结果就是出现如下异常信息。

1
2
Exception in thread "main" java.io.NotSerializableException:
...

serialVersionUID

请注意 serialVersionUID 字段,你可以在 Java 世界的无数类中看到这个字段。

serialVersionUID 有什么作用,如何使用 serialVersionUID

serialVersionUID 是 Java 为每个序列化类产生的版本标识。它可以用来保证在反序列时,发送方发送的和接受方接收的是可兼容的对象。如果接收方接收的类的 serialVersionUID 与发送方发送的 serialVersionUID 不一致,会抛出 InvalidClassException

如果可序列化类没有显式声明 serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID 值。尽管这样,还是建议在每一个序列化的类中显式指定 serialVersionUID 的值。因为不同的 jdk 编译很可能会生成不同的 serialVersionUID 默认值,从而导致在反序列化时抛出 InvalidClassExceptions 异常。

serialVersionUID 字段必须是 static final long 类型

我们来举个例子:

(1)有一个可序列化类 Person

1
2
3
4
5
6
7
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private Integer age;
private String address;
// 构造方法、get、set 方法略
}

(2)开发过程中,对 Person 做了修改,增加了一个字段 email,如下:

1
2
3
4
5
6
7
8
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private Integer age;
private String address;
private String email;
// 构造方法、get、set 方法略
}

由于这个类和老版本不兼容,我们需要修改版本号:

1
private static final long serialVersionUID = 2L;

再次进行反序列化,则会抛出 InvalidClassException 异常。

综上所述,我们大概可以清楚:**serialVersionUID 用于控制序列化版本是否兼容**。若我们认为修改的可序列化类是向后兼容的,则不修改 serialVersionUID

默认序列化机制

如果仅仅只是让某个类实现 Serializable 接口,而没有其它任何处理的话,那么就会使用默认序列化机制。

使用默认机制,在序列化对象时,不仅会序列化当前对象本身,还会对其父类的字段以及该对象引用的其它对象也进行序列化。同样地,这些其它对象引用的另外对象也将被序列化,以此类推。所以,如果一个对象包含的成员变量是容器类对象,而这些容器所含有的元素也是容器类对象,那么这个序列化的过程就会较复杂,开销也较大。

🔔 注意:这里的父类和引用对象既然要进行序列化,那么它们当然也要满足序列化要求:被序列化的类必须属于 Enum、Array 和 Serializable 类型其中的任何一种

JDK 序列化要点

Java 的序列化能保证对象状态的持久保存,但是遇到一些对象结构复杂的情况还是难以处理,这里归纳一下:

  • 父类是 Serializable,所有子类都可以被序列化。
  • 子类是 Serializable ,父类不是,则子类可以正确序列化,但父类的属性不会被序列化(不报错,数据丢失)。
  • 如果序列化的属性是对象,则这个对象也必须是 Serializable ,否则报错。
  • 反序列化时,如果对象的属性有修改或删减,则修改的部分属性会丢失,但不会报错。
  • 反序列化时,如果 serialVersionUID 被修改,则反序列化会失败。

transient

在现实应用中,有些时候不能使用默认序列化机制。比如,希望在序列化过程中忽略掉敏感数据,或者简化序列化过程。下面将介绍若干影响序列化的方法。

序列化时,默认序列化机制会忽略被声明为 transient 的字段,该字段的内容在序列化后无法访问。

我们将 SerializeDemo01 示例中的内部类 Person 的 age 字段声明为 transient,如下所示:

1
2
3
4
5
6
7
8
9
public class SerializeDemo02 {
static class Person implements Serializable {
transient private Integer age = null;
// 其他内容略
}
// 其他内容略
}
// Output:
// name: Jack, age: null, sex: MALE

从输出结果可以看出,age 字段没有被序列化。

Externalizable 接口

无论是使用 transient 关键字,还是使用 writeObject()readObject() 方法,其实都是基于 Serializable 接口的序列化。

JDK 中提供了另一个序列化接口–Externalizable

可序列化类实现 Externalizable 接口之后,基于 Serializable 接口的默认序列化机制就会失效

我们来基于 SerializeDemo02 再次做一些改动,代码如下:

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
public class ExternalizeDemo01 {
static class Person implements Externalizable {
transient private Integer age = null;
// 其他内容略

private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeInt(age);
}

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
age = in.readInt();
}

@Override
public void writeExternal(ObjectOutput out) throws IOException { }

@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { }
}
// 其他内容略
}
// Output:
// call Person()
// name: null, age: null, sex: null

从该结果,一方面可以看出 Person 对象中任何一个字段都没有被序列化。另一方面,如果细心的话,还可以发现这此次序列化过程调用了 Person 类的无参构造方法。

  • Externalizable 继承于 Serializable,它增添了两个方法:writeExternal()readExternal()。这两个方法在序列化和反序列化过程中会被自动调用,以便执行一些特殊操作。当使用该接口时,序列化的细节需要由程序员去完成。如上所示的代码,由于 writeExternal()readExternal() 方法未作任何处理,那么该序列化行为将不会保存/读取任何一个字段。这也就是为什么输出结果中所有字段的值均为空。
  • 另外,若使用 Externalizable 进行序列化,当读取对象时,会调用被序列化类的无参构造方法去创建一个新的对象;然后再将被保存对象的字段的值分别填充到新对象中。这就是为什么在此次序列化过程中 Person 类的无参构造方法会被调用。由于这个原因,实现 Externalizable 接口的类必须要提供一个无参的构造方法,且它的访问权限为 public

对上述 Person 类作进一步的修改,使其能够对 name 与 age 字段进行序列化,但要忽略掉 gender 字段,如下代码所示:

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 ExternalizeDemo02 {
static class Person implements Externalizable {
transient private Integer age = null;
// 其他内容略

private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeInt(age);
}

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
age = in.readInt();
}

@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
out.writeInt(age);
}

@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name = (String) in.readObject();
age = in.readInt();
}
}
// 其他内容略
}
// Output:
// call Person()
// name: Jack, age: 30, sex: null

Externalizable 接口的替代方法

实现 Externalizable 接口可以控制序列化和反序列化的细节。它有一个替代方法:实现 Serializable 接口,并添加 writeObject(ObjectOutputStream out)readObject(ObjectInputStream in) 方法。序列化和反序列化过程中会自动回调这两个方法。

示例如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class SerializeDemo03 {
static class Person implements Serializable {
transient private Integer age = null;
// 其他内容略

private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeInt(age);
}

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
age = in.readInt();
}
// 其他内容略
}
// 其他内容略
}
// Output:
// name: Jack, age: 30, sex: MALE

writeObject() 方法中会先调用 ObjectOutputStream 中的 defaultWriteObject() 方法,该方法会执行默认的序列化机制,如上节所述,此时会忽略掉 age 字段。然后再调用 writeInt() 方法显示地将 age 字段写入到 ObjectOutputStream 中。readObject() 的作用则是针对对象的读取,其原理与 writeObject() 方法相同。

🔔 注意:writeObject()readObject() 都是 private 方法,那么它们是如何被调用的呢?毫无疑问,是使用反射。详情可见 ObjectOutputStream 中的 writeSerialData 方法,以及 ObjectInputStream 中的 readSerialData 方法。

readResolve() 方法

当我们使用 Singleton 模式时,应该是期望某个类的实例应该是唯一的,但如果该类是可序列化的,那么情况可能会略有不同。此时对第 2 节使用的 Person 类进行修改,使其实现 Singleton 模式,如下所示:

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
public class SerializeDemo04 {

enum Sex {
MALE, FEMALE
}

static class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name = null;
transient private Integer age = null;
private Sex sex;
static final Person instatnce = new Person("Tom", 31, Sex.MALE);

private Person() {
System.out.println("call Person()");
}

private Person(String name, Integer age, Sex sex) {
this.name = name;
this.age = age;
this.sex = sex;
}

public static Person getInstance() {
return instatnce;
}

private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeInt(age);
}

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
age = in.readInt();
}

public String toString() {
return "name: " + this.name + ", age: " + this.age + ", sex: " + this.sex;
}
}

/**
* 序列化
*/
private static void serialize(String filename) throws IOException {
File f = new File(filename); // 定义保存路径
OutputStream out = new FileOutputStream(f); // 文件输出流
ObjectOutputStream oos = new ObjectOutputStream(out); // 对象输出流
oos.writeObject(new Person("Jack", 30, Sex.MALE)); // 保存对象
oos.close();
out.close();
}

/**
* 反序列化
*/
private static void deserialize(String filename) throws IOException, ClassNotFoundException {
File f = new File(filename); // 定义保存路径
InputStream in = new FileInputStream(f); // 文件输入流
ObjectInputStream ois = new ObjectInputStream(in); // 对象输入流
Object obj = ois.readObject(); // 读取对象
ois.close();
in.close();
System.out.println(obj);
System.out.println(obj == Person.getInstance());
}

public static void main(String[] args) throws IOException, ClassNotFoundException {
final String filename = "d:/text.dat";
serialize(filename);
deserialize(filename);
}
}
// Output:
// name: Jack, age: null, sex: MALE
// false

值得注意的是,从文件中获取的 Person 对象与 Person 类中的单例对象并不相等。为了能在单例类中仍然保持序列的特性,可以使用 readResolve() 方法。在该方法中直接返回 Person 的单例对象。我们在 SerializeDemo04 示例的基础上添加一个 readResolve 方法, 如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class SerializeDemo05 {
// 其他内容略

static class Person implements Serializable {

// private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
// in.defaultReadObject();
// age = in.readInt();
// }

// 添加此方法
private Object readResolve() {
return instatnce;
}
// 其他内容略
}

// 其他内容略
}
// Output:
// name: Tom, age: 31, sex: MALE
// true

JDK 序列化的问题

  • 无法跨语言:JDK 序列化目前只适用基于 Java 语言实现的框架,其它语言大部分都没有使用 Java 的序列化框架,也没有实现 JDK 序列化这套协议。因此,如果是两个基于不同语言编写的应用程序相互通信,则无法实现两个应用服务之间传输对象的序列化与反序列化。
  • 容易被攻击:对象是通过在 ObjectInputStream 上调用 readObject() 方法进行反序列化的,它可以将类路径上几乎所有实现了 Serializable 接口的对象都实例化。这意味着,在反序列化字节流的过程中,该方法可以执行任意类型的代码,这是非常危险的。对于需要长时间进行反序列化的对象,不需要执行任何代码,也可以发起一次攻击。攻击者可以创建循环对象链,然后将序列化后的对象传输到程序中反序列化,这种情况会导致 hashCode 方法被调用次数呈次方爆发式增长, 从而引发栈溢出异常。例如下面这个案例就可以很好地说明。
  • 序列化后的流太大:JDK 序列化中使用了 ObjectOutputStream 来实现对象转二进制编码,编码后的数组很大,非常影响存储和传输效率。
  • 序列化性能太差:Java 的序列化耗时比较大。序列化的速度也是体现序列化性能的重要指标,如果序列化的速度慢,就会影响网络通信的效率,从而增加系统的响应时间。
  • 序列化编程限制
    • JDK 序列化一定要实现 Serializable 接口
    • JDK 序列化**需要关注 serialVersionUID**。

二进制序列化

上节详细介绍了 JDK 序列化方式,由于其性能不高,且存在很多其他问题,所以业界有了很多其他优秀的二进制序列化库。

Protobuf

Protobuf 是 Google 公司内部的混合语言数据标准,是一种轻便、高效的结构化数据存储格式,可以用于结构化数据序列化,支持 Java、Python、C++、Go 等语言。Protobuf 使用的时候需要定义 IDL(Interface description language),然后使用不同语言的 IDL
编译器,生成序列化工具类。

优点:

  • 序列化后体积相比 JSON、Hessian 小很多
  • 序列化反序列化速度很快,不需要通过反射获取类型
  • 语言和平台无关(基于 IDL),IDL 能清晰地描述语义,所以足以帮助并保证应用程序之间的类型不会丢失,无需类似 XML 解析器
  • 消息格式升级和兼容性不错,可以做到后向兼容
  • 支持 Java, C++, Python 三种语言

缺点:

  • Protobuf 对于具有反射和动态能力的语言来说,用起来很费劲。

Thrift

Thrift 是 apache 开源项目,是一个点对点的 RPC 实现。

它具有以下特性:

  • 支持多种语言(目前支持 28 种语言,如:C++、go、Java、Php、Python、Ruby 等等)。
  • 使用了组建大型数据交换及存储工具,对于大型系统中的内部数据传输,相对于 Json 和 xml 在性能上和传输大小上都有明显的优势。
  • 支持三种比较典型的编码方式(通用二进制编码,压缩二进制编码,优化的可选字段压缩编解码)。

Hessian

Hessian 是动态类型、二进制、紧凑的,并且可跨语言移植的一种序列化框架。Hessian 协议要比 JDK、JSON 更加紧凑,性能上要比 JDK、JSON 序列化高效很多,而且生成的字节数也更小。

RPC 框架 Dubbo 就支持 Thrift 和 Hession。

它具有以下特性:

  • 支持多种语言。如:Java、Python、C++、C#、PHP、Ruby 等。
  • 相对其他二进制序列化库较慢。

Hessian 本身也有问题,官方版本对 Java 里面一些常见对象的类型不支持:

  • Linked 系列,LinkedHashMap、LinkedHashSet 等,但是可以通过扩展 CollectionDeserializer 类修复;
  • Locale 类,可以通过扩展 ContextSerializerFactory 类修复;
  • Byte/Short 反序列化的时候变成 Integer。

Kryo

Kryo 是用于 Java 的快速高效的二进制对象图序列化框架。Kryo 还可以执行自动的深拷贝和浅拷贝。 这是从对象到对象的直接复制,而不是从对象到字节的复制。

它具有以下特性:

  • 速度快,序列化体积小
  • 官方不支持 Java 以外的其他语言

FST

FST 是一个 Java 实现二进制序列化库。

它具有以下特性:

  • 近乎于 100% 兼容 JDK 序列化,且比 JDK 原序列化方式快 10 倍
  • 2.17 开始与 Android 兼容
  • (可选)2.29 开始支持将任何可序列化的对象图编码/解码为 JSON(包括共享引用)

JSON 序列化

除了二进制序列化方式,还可以选择 JSON 序列化。它的性能比二进制序列化方式差,但是可读性非常好,且广泛应用于 Web 领域。

JSON 是什么

JSON 起源于 1999 年的 JS 语言规范 ECMA262 的一个子集(即 15.12 章节描述了格式与解析),后来 2003 年作为一个数据格式ECMA404(很囧的序号有不有?)发布。
2006 年,作为 rfc4627 发布,这时规范增加到 18 页,去掉没用的部分,十页不到。

JSON 的应用很广泛,这里有超过 100 种语言下的 JSON 库:json.org

更多的可以参考这里,关于 json 的一切

JSON 标准

这估计是最简单标准规范之一:

  • 只有两种结构:对象内的键值对集合结构和数组,对象用 {} 表示、内部是 "key":"value",数组用 [] 表示,不同值用逗号分开
  • 基本数值有 7 个: false / null / true / object / array / number / string
  • 再加上结构可以嵌套,进而可以用来表达复杂的数据
  • 一个简单实例:
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"Image": {
"Width": 800,
"Height": 600,
"Title": "View from 15th Floor",
"Thumbnail": {
"Url": "http://www.example.com/image/481989943",
"Height": 125,
"Width": "100"
},
"IDs": [116, 943, 234, 38793]
}
}

扩展阅读:

JSON 优缺点

JSON 优点

  • 基于纯文本,所以对于人类阅读是很友好的。
  • 规范简单,所以容易处理,开箱即用,特别是 JS 类的 ECMA 脚本里是内建支持的,可以直接作为对象使用。
  • 平台无关性,因为类型和结构都是平台无关的,而且好处理,容易实现不同语言的处理类库,可以作为多个不同异构系统之间的数据传输格式协议,特别是在 HTTP/REST 下的数据格式。

JSON 缺点

  • 性能一般,文本表示的数据一般来说比二进制大得多,在数据传输上和解析处理上都要更影响性能。
  • 缺乏 schema,跟同是文本数据格式的 XML 比,在类型的严格性和丰富性上要差很多。XML 可以借由 XSD 或 DTD 来定义复杂的格式,并由此来验证 XML 文档是否符合格式要求,甚至进一步的,可以基于 XSD 来生成具体语言的操作代码,例如 apache xmlbeans。并且这些工具组合到一起,形成一套庞大的生态,例如基于 XML 可以实现 SOAP 和 WSDL,一系列的 ws-*规范。但是我们也可以看到 JSON 在缺乏规范的情况下,实际上有更大一些的灵活性,特别是近年来 REST 的快速发展,已经有一些 schema 相关的发展(例如理解 JSON Schema使用 JSON Schema在线 schema 测试),也有类似于 WSDL 的WADL出现。

JSON 库

Java 中比较流行的 JSON 库有:

  • Fastjson - 阿里巴巴开发的 JSON 库,性能十分优秀。
  • Jackson - 社区十分活跃且更新速度很快。Spring 框架默认 JSON 库。
  • Gson - 谷歌开发的 JSON 库,目前功能最全的 JSON 库 。

从性能上来看,一般情况下:Fastjson > Jackson > Gson

JSON 编码指南

遵循好的设计与编码风格,能提前解决 80%的问题,个人推荐 Google JSON 风格指南。

简单摘录如下:

  • 属性名和值都是用双引号,不要把注释写到对象里面,对象数据要简洁
  • 不要随意结构化分组对象,推荐是用扁平化方式,层次不要太复杂
  • 命名方式要有意义,比如单复数表示
  • 驼峰式命名,遵循 Bean 规范
  • 使用版本来控制变更冲突
  • 对于一些关键字,不要拿来做 key
  • 如果一个属性是可选的或者包含空值或 null 值,考虑从 JSON 中去掉该属性,除非它的存在有很强的语义原因
  • 序列化枚举类型时,使用 name 而不是 value
  • 日期要用标准格式处理
  • 设计好通用的分页参数
  • 设计好异常处理

JSON API与 Google JSON 风格指南有很多可以相互参照之处。

JSON API是数据交互规范,用以定义客户端如何获取与修改资源,以及服务器如何响应对应请求。

JSON API 设计用来最小化请求的数量,以及客户端与服务器间传输的数据量。在高效实现的同时,无需牺牲可读性、灵活性和可发现性。

序列化技术选型

市面上有如此多的序列化技术,那么我们在应用时如何选择呢?

序列化技术选型,需要考量的维度,根据重要性从高到低,依次有:

  • 安全性:是否存在漏洞。如果存在漏洞,就有被攻击的可能性。
  • 兼容性:版本升级后的兼容性是否很好,是否支持更多的对象类型,是否是跨平台、跨语言的。服务调用的稳定性与可靠性,要比服务的性能更加重要。
  • 性能
    • 时间开销:序列化、反序列化的耗时性能自然越小越好。
    • 空间开销:序列化后的数据越小越好,这样网络传输效率就高。
  • 易用性:类库是否轻量化,API 是否简单易懂。

鉴于以上的考量,序列化技术的选型建议如下:

  • JDK 序列化:性能较差,且有很多使用限制,不建议使用。
  • ThriftProtobuf:适用于对性能敏感,对开发体验要求不高
  • Hessian:适用于对开发体验敏感,性能有要求
  • JacksonGsonFastjson:适用于对序列化后的数据要求有良好的可读性(转为 json 、xml 形式)。

参考资料

Spring 集成 Mybatis

Mybatis 官网 是一款持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

快速入门

要使用 MyBatis, 只需将 mybatis-x.x.x.jar 文件置于类路径(classpath)中即可。

如果使用 Maven 来构建项目,则需将下面的依赖代码置于 pom.xml 文件中:

1
2
3
4
5
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>x.x.x</version>
</dependency>

从 XML 中构建 SqlSessionFactory

每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。

从 XML 文件中构建 SqlSessionFactory 的实例非常简单,建议使用类路径下的资源文件进行配置。 但也可以使用任意的输入流(InputStream)实例,比如用文件路径字符串或 file:// URL 构造的输入流。MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,使得从类路径或其它位置加载资源文件更加容易。

1
2
3
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

XML 配置文件中包含了对 MyBatis 系统的核心设置,包括获取数据库连接实例的数据源(DataSource)以及决定事务作用域和控制方式的事务管理器(TransactionManager)。后面会再探讨 XML 配置文件的详细内容,这里先给出一个简单的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration>

当然,还有很多可以在 XML 文件中配置的选项,上面的示例仅罗列了最关键的部分。 注意 XML 头部的声明,它用来验证 XML 文档的正确性。environment 元素体中包含了事务管理和连接池的配置。mappers 元素则包含了一组映射器(mapper),这些映射器的 XML 映射文件包含了 SQL 代码和映射定义信息。

不使用 XML 构建 SqlSessionFactory

如果你更愿意直接从 Java 代码而不是 XML 文件中创建配置,或者想要创建你自己的配置构建器,MyBatis 也提供了完整的配置类,提供了所有与 XML 文件等价的配置项。

1
2
3
4
5
6
DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(BlogMapper.class);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

注意该例中,configuration 添加了一个映射器类(mapper class)。映射器类是 Java 类,它们包含 SQL 映射注解从而避免依赖 XML 映射文件。不过,由于 Java 注解的一些限制以及某些 MyBatis 映射的复杂性,要使用大多数高级映射(比如:嵌套联合映射),仍然需要使用 XML 映射文件进行映射。有鉴于此,如果存在一个同名 XML 映射文件,MyBatis 会自动查找并加载它(在这个例子中,基于类路径和 BlogMapper.class 的类名,会加载 BlogMapper.xml)。具体细节稍后讨论。

从 SqlSessionFactory 中获取 SqlSession

既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。例如:

1
2
3
try (SqlSession session = sqlSessionFactory.openSession()) {
Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
}

诚然,这种方式能够正常工作,对使用旧版本 MyBatis 的用户来说也比较熟悉。但现在有了一种更简洁的方式——使用和指定语句的参数和返回值相匹配的接口(比如 BlogMapper.class),现在你的代码不仅更清晰,更加类型安全,还不用担心可能出错的字符串字面值以及强制类型转换。

例如:

1
2
3
4
try (SqlSession session = sqlSessionFactory.openSession()) {
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);
}

现在我们来探究一下这段代码究竟做了些什么。

探究已映射的 SQL 语句

现在你可能很想知道 SqlSession 和 Mapper 到底具体执行了些什么操作,但 SQL 语句映射是个相当广泛的话题,可能会占去文档的大部分篇幅。 但为了让你能够了解个大概,这里先给出几个例子。

在上面提到的例子中,一个语句既可以通过 XML 定义,也可以通过注解定义。我们先看看 XML 定义语句的方式,事实上 MyBatis 提供的所有特性都可以利用基于 XML 的映射语言来实现,这使得 MyBatis 在过去的数年间得以流行。如果你用过旧版本的 MyBatis,你应该对这个概念比较熟悉。 但相比于之前的版本,新版本改进了许多 XML 的配置,后面我们会提到这些改进。这里给出一个基于 XML 映射语句的示例,它应该可以满足上个示例中 SqlSession 的调用。

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.mybatis.example.BlogMapper">
<select id="selectBlog" resultType="Blog">
select * from Blog where id = #{id}
</select>
</mapper>

为了这个简单的例子,我们似乎写了不少配置,但其实并不多。在一个 XML 映射文件中,可以定义无数个映射语句,这样一来,XML 头部和文档类型声明部分就显得微不足道了。文档的其它部分很直白,容易理解。 它在命名空间 “org.mybatis.example.BlogMapper” 中定义了一个名为 “selectBlog” 的映射语句,这样你就可以用全限定名 “org.mybatis.example.BlogMapper.selectBlog” 来调用映射语句了,就像上面例子中那样:

1
Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);

你可能会注意到,这种方式和用全限定名调用 Java 对象的方法类似。这样,该命名就可以直接映射到在命名空间中同名的映射器类,并将已映射的 select 语句匹配到对应名称、参数和返回类型的方法。因此你就可以像上面那样,不费吹灰之力地在对应的映射器接口调用方法,就像下面这样:

1
2
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);

第二种方法有很多优势,首先它不依赖于字符串字面值,会更安全一点;其次,如果你的 IDE 有代码补全功能,那么代码补全可以帮你快速选择到映射好的 SQL 语句。

提示 对命名空间的一点补充

在之前版本的 MyBatis 中,命名空间(Namespaces)的作用并不大,是可选的。 但现在,随着命名空间越发重要,你必须指定命名空间。

命名空间的作用有两个,一个是利用更长的全限定名来将不同的语句隔离开来,同时也实现了你上面见到的接口绑定。就算你觉得暂时用不到接口绑定,你也应该遵循这里的规定,以防哪天你改变了主意。 长远来看,只要将命名空间置于合适的 Java 包命名空间之中,你的代码会变得更加整洁,也有利于你更方便地使用 MyBatis。

命名解析:为了减少输入量,MyBatis 对所有具有名称的配置元素(包括语句,结果映射,缓存等)使用了如下的命名解析规则。

  • 全限定名(比如 “com.mypackage.MyMapper.selectAllThings)将被直接用于查找及使用。
  • 短名称(比如 “selectAllThings”)如果全局唯一也可以作为一个单独的引用。 如果不唯一,有两个或两个以上的相同名称(比如 “com.foo.selectAllThings” 和 “com.bar.selectAllThings”),那么使用时就会产生“短名称不唯一”的错误,这种情况下就必须使用全限定名。

对于像 BlogMapper 这样的映射器类来说,还有另一种方法来完成语句映射。 它们映射的语句可以不用 XML 来配置,而可以使用 Java 注解来配置。比如,上面的 XML 示例可以被替换成如下的配置:

1
2
3
4
5
package org.mybatis.example;
public interface BlogMapper {
@Select("SELECT * FROM blog WHERE id = #{id}")
Blog selectBlog(int id);
}

使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。

选择何种方式来配置映射,以及是否应该要统一映射语句定义的形式,完全取决于你和你的团队。 换句话说,永远不要拘泥于一种方式,你可以很轻松地在基于注解和 XML 的语句映射方式间自由移植和切换。

作用域(Scope)和生命周期

理解我们之前讨论过的不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。

提示 对象生命周期和依赖注入框架

依赖注入框架可以创建线程安全的、基于事务的 SqlSession 和映射器,并将它们直接注入到你的 bean 中,因此可以直接忽略它们的生命周期。 如果对如何通过依赖注入框架使用 MyBatis 感兴趣,可以研究一下 MyBatis-Spring 或 MyBatis-Guice 两个子项目。

SqlSessionFactoryBuilder

这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。

SqlSessionFactory

SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。

SqlSession

每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。 下面的示例就是一个确保 SqlSession 关闭的标准模式:

1
2
3
try (SqlSession session = sqlSessionFactory.openSession()) {
// 你的应用逻辑代码
}

在所有代码中都遵循这种使用模式,可以保证所有数据库资源都能被正确地关闭。

映射器实例

映射器是一些绑定映射语句的接口。映射器接口的实例是从 SqlSession 中获得的。虽然从技术层面上来讲,任何映射器实例的最大作用域与请求它们的 SqlSession 相同。但方法作用域才是映射器实例的最合适的作用域。 也就是说,映射器实例应该在调用它们的方法中被获取,使用完毕之后即可丢弃。 映射器实例并不需要被显式地关闭。尽管在整个请求作用域保留映射器实例不会有什么问题,但是你很快会发现,在这个作用域上管理太多像 SqlSession 的资源会让你忙不过来。 因此,最好将映射器放在方法作用域内。就像下面的例子一样:

1
2
3
4
try (SqlSession session = sqlSessionFactory.openSession()) {
BlogMapper mapper = session.getMapper(BlogMapper.class);
// 你的应用逻辑代码
}

Mybatis 扩展工具

Mybatis Plus

MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

【集成示例】spring-boot-data-mybatis-plus

Mapper

Mapper 是一个 Mybatis CRUD 扩展插件。

Mapper 的基本原理是将实体类映射为数据库中的表和字段信息,因此实体类需要通过注解配置基本的元数据,配置好实体后, 只需要创建一个继承基础接口的 Mapper 接口就可以开始使用了。

【集成示例】spring-boot-data-mybatis-mapper

PageHelper

PageHelper 是一个 Mybatis 通用分页插件。

【集成示例】spring-boot-data-mybatis-mapper

参考资料

Windows 常用技巧总结

软件

扩展阅读:

视频音频

  • Musicbee - 类似 iTunes,但比 iTunes 更好用。
  • ScreenToGif - 它允许你录制屏幕的一部分区域并保存为 gif 或视频。
  • PotPlayer - 多媒体播放器,具有广泛的编解码器集合,它还为用户提供大量配置选项。
  • 射手影音播放器 - 来自射手网,小巧开源,首创自动匹配字幕功能。

压缩

  • 7-Zip - 用于处理压缩包的开源 Windows 实用程序。完美支持 7z,ZIP,GZIP,BZIP2 和 TAR 的全部特性,其他格式也可解压缩。
  • WinRAR - 强大的归档管理器。 它可以备份您的数据并减小电子邮件附件的大小,解压缩 RAR,ZIP 和其他文件。

文件管理

  • Clover - 为资源管理器加上多标签功能。
  • Total Commander - 老牌、功能异常强大的文件管理增强软件。
  • Q-Dir - 轻量级的文件管理器,各种布局视图切换灵活,默认四个小窗口组成一个大窗口,操作快捷。软件虽小,粉丝忠诚。
  • WoX - 新一代文件定位工具,堪称 Windows 上的 Alfred。
  • Everything - 最快的文件/文件夹搜索工具, 通过名称搜索。
  • Listary - 非常优秀的 Windows 文件浏览和搜索增强工具。
  • Beyond Compare - 好用又万能的文件对比工具。
  • CCleaner - 如果你有系统洁癖,那一定要选择一款干净、良心、老牌的清洁软件。
  • chocolatey - 包管理器
  • Ninite - 最简单,最快速的更新或安装软件的方式。
  • Recuva - 来自 piriform 梨子公司产品,免费的数据恢复工具。
  • Launchy:自由的跨平台工具,帮助你忘记开始菜单、桌面图标甚至文件管理器。

开发

  • Fiddler - web 调试代理工具。
  • Postman - 适合 API 开发的完整工具链,最常用的 REST 客户端。
  • SourceTree - 一个免费的 Git & Mercurial 客户端。
  • TortoiseSVN - Subversion(SVN)的图形客户端
  • Wireshark - 一个网络协议分析工具。
  • Switchhosts
  • Cmder - 控制台模拟器包。扩展阅读:Win 下必备神器之 Cmder
  • Babun - 基于 Cygwin,用于替代 Windows shell。

编辑器

  • JetBrain IDE 系列 - 真香!
  • Visual Studio Code - 用于构建和调试现代 Web 和云应用程序。
  • Eclipse - 一款功能强大的 IDE。
  • Visual Studio - 微软官方的 IDE,通过插件可支持大量编程语言。
  • NetBeans IDE - 免费开源的 IDE。
  • Typora - 个人觉得最好用的 Markdown 编辑器。
  • Cmd Markdown - 跨平台优秀 Markdown 编辑器,本文即用其所写。
  • Notepad++ - 一款支持多种编程语言的源码编辑器。
  • Notepad2 - 用于替代默认文本编辑器的轻量快速的编辑器,拥有众多有用的功能。
  • Sublime Text 3 - 高级文本编辑器。
  • Atom - 面向 21 世纪的极客文本编辑器。

文档

  • Microsoft Office - 微软办公软件。
  • WPS Office - 金山免费办公软件。
  • Calibre - 用于电子书管理和转换的强大软件。
  • 福昕阅读器 - 在全球拥有大量用户,最优秀的国产软件之一。Ribbon 界面,支持手写签名、插入印章等。

效率提升

【笔记】

  • XMind - 优秀的思维导图。
  • OneNote - Windows 下综合评价非常高的笔记应用。
  • 印象笔记 - 老牌跨平台笔记工具,国际版 Evernote。一家立志于做百年公司的企业,安全、可靠。
  • 为知笔记 - 越来越好的笔记应用,记录、查阅一切有价值的信息,同样跨平台支持。
  • 有道云笔记 - 网易旗下笔记工具,同样跨主流平台支持,文字、手写、录音、拍照多种记录方式,支持任意附件格式。
  • ShareX - 你要的所有与截图、录屏相关的功能,这里都有了。

【快捷键】

  • AutoHotkey - Windows 平台的终极自动化脚本语言。

技巧:

办公

  • 有道词典 - 最好用的免费全能翻译软件。
  • Outlook - 大名鼎鼎的 Microsoft Office 组件之一,除了电子邮件,还包含了日历、任务管理、联系人、记事本等功能。
  • Gmail - 功能上可以称为业界标杆,用户数量世界第一,或许你真的找不到比它更好的邮件系统。
  • Chrome - 最好的浏览器。
  • Teamviewer - 专业、功能强大的远程控制软件。使用简单,对个人用户免费。

个性化

  • TranslucentTB - 透明化你的 Windows 任务栏。
  • QTTabBar - 通过多标签和额外的文件夹视图扩展资源管理器的功能。
  • Fences - 管理桌面快捷方式。

参考资料

基本操作

软件管理

dmg 格式:双击安装包,然后拖到 applications 文件夹下即可。

浏览器

更改默认搜索引擎

选择“偏好设置–>搜索–>搜索引擎–>Google”。

导入 chrome 浏览器的书签

选择“文件–>导入自–> Google Chrome”,然后选择要导入的项目。

快捷键

Command + R 刷新

上方显示书签栏/收藏栏

选择“显示–> 显示个人收藏栏”。

关闭软件的右上角通知

在 Mac 系统中有对通知的设置,打开系统偏好设置 — 通知 找到 QQ,然后将 QQ 提示样式设置成无即可。

复制文件/文件夹路径

  • OS X 10.11 系统,选中文件夹,“cmd +Option +c” 复制文件夹路径,cmd+v 粘贴。
    之前的系统,利用 Administrator 创建一个到右键菜单,然后到设置里面设置快捷键。具体操作请百度。

打开来自身份不明的开发者的应用程序

在应用程序文件夹,按住 control 键的同时打开应用程序。

复制文件路径

  • 选择文件/文件夹按 Command+C 复制,在终端中 Command+V 粘贴即可。

  • 如果只是想在 Finder 中看到文件的路径, 并方便切换层级, Finder 内置了“显示路径栏”的功能, 并配置了快捷键(Option+Cmd+P). 如下图所示:

20161124-184148.png

参考链接:

隐藏和取消隐藏 Mac App Store 中的已购项目

Mac 同时登陆两个 QQ

在已经打开的 QQ 中,按住“command + N”即可。

系统便好设置

语音播报

打开“系统便好设置–>辅助功能–>语音”,即可设置不同国家的语言。

勾选上图中的红框部分,可以设置全局快捷键。这样的话,在任何一个软件当中,按下“ option+esc”时,就会朗读选中的文本。

调整字体大小

Mac 调整字体大小:“系统偏好设置 -> 显示器 -> 缩放”。如下图:

如何分别设置 Mac 的鼠标和触控板的滚动方向

很多人习惯鼠标使用相反的滚动方向,而触控板类似 iPad 那样的自然滚动,问如何设置,当时我的回答是不知道,因为目前 OS X 的系统设置里,鼠标和触控板的设置是统一
的。今天发现了一个免费的软件 Scroll Reverser,可以实现鼠标和触控板的分别设置。下载地址:https://pilotmoon.com/scrollreverser/
启动后程序显示在顶部菜单栏,设置简单明了,有需要的用户体验一下吧。

Touch Bar 自定义

打开“系统偏好设置-键盘”,下面有个自定义控制条。

色温调节:夜间模式

iOS9.3 的最明显变化,莫过于苹果在发布会上特意提到的 Night Shift 夜间护眼模式。

iCloud 邮箱

如果您用于设置 iCloud 的 Apple ID 不以“@icloud.com”、“@me.com”或“@mac.com”结尾,您必须先设置一个“@icloud.com”电子邮件地址,然后才能使用 iCloud“邮件”。

如果您拥有以“@mac.com”或“@me.com”结尾的电子邮件地址,则您已经拥有了名称相同但以“@icloud.com”结尾的等效地址。如果您使用的电子邮件别名以“@mac.com”或“@me.com”结尾,您也将拥有以“@icloud.com”结尾的等效地址。

操作如下:

  • 在 iOS 设备上,前往“设置”>“iCloud”,开启“邮件”,然后按照屏幕上的说明操作。

  • 在 Mac 上,选取 Apple 菜单 >“系统偏好设置”,点按“iCloud”,再选择“邮件”,然后按照屏幕上的说明操作。

PS:创建 iCloud 电子邮件地址后,您无法对其进行更改。

设置 @icloud.com 电子邮件地址后即可用其登录 iCloud。您也可以用创建 iCloud 帐户时所用的 Apple ID 登录。

您可以从以下任意地址发送 iCloud 电子邮件:

您的 iCloud 电子邮件地址(您的帐号名称@icloud.com)

别名

参考链接:

直接注册以@icloud.com 结尾的 Apple ID:

参考链接:

PodCast

PodCast 中文翻译为播客,是一种特殊的音频 or 视频节目。PodCast 这个单词是由 iPod+Broadcast 这两个单词组成的。

PodCast 可以在 iTunes 中收听。

others

词典

系统有一个自带应用“词典”,可以进行单词的查询。

如何解决 MAC 软件(dmg,akp,app)出现程序已损坏的提示

“xxx.app 已损坏,打不开.你应该将它移到废纸篓”,并非你安装的软件已损坏,而是 Mac 系统的安全设置问题,因为这些应用都是破解或者汉化的,那么解决方法就是临时改变 Mac 系统安全设置。

出现这个问题的解决方法:修改系统配置:系统偏好设置… -> 安全性与隐私。修改为任何来源。

如果没有这个选项的话(macOS Sierra 10.12),打开终端,执行:

1
sudo spctl --master-disable

即可。

参考链接:

备注:这个链接里的各种资源都很不错啊。

终端

在 Finder 的当前目录打开终端

在 Finder 打开 terminal 终端这个功能其实是有的,但是系统默认没有打开。我们可以通过如下方法将其打开:

进入系统偏好设置->键盘->快捷键->服务。

在右边新建位于文件夹位置的终端窗口上打勾。

如此设置后,在 Finder 中右击某文件,在出现的菜单中找到服务,然后点击新建位于文件夹位置的终端窗口即可!

Mac 常用快捷键

Finder

快捷键 作用 备注
Shift + Command + G 前往指定路径的文件夹 包括隐藏文件夹
Shift + Command + . 显示隐藏文件、文件夹 再按一次,恢复隐藏
Command + ↑ 返回上一层
Command + ↓ 进入当前文件夹

编辑

删除文字

快捷键 作用 备注
delete 删除光标的前一个字符 相当于 Windows 键盘上的退格键
fn + delete 删除光标的后一个字符
option + delete 删除光标之前的一个单词 英文有效
command + delete 删除光标之前的整行内容 【荐】
command + delete 在 finder 中删掉该文件
shift + command + delete 清空回收站

剪切文件

首先选中文件,按 Command+C 复制文件;然后按“Command + Option + V”剪切文件。

备注:Command+X 只能剪切文字文本,不要混淆了。

Mac 用户必须知道的 15 组快捷键

参考链接:《轻松玩 Mac》第 6 期:Mac 用户必须知道的 15 组快捷键

“space”键:快速预览

选中文件后, 不需要启动任何应用程序,使用“space”空格键可进行快速预览,再次按下“space”空格键取消预览。

可以预览 mp3、视频、pdf 等文件。

我们还可以选中多张图片, 然后按“space”键,就可以同时对比预览多张图片。这一点,很赞。

改名

选中文件/文件夹后,按 enter 键,就可以改名了。

“command + I”键:查看文件属性

  • 选中文件后,按“command + I”键,可以查看文件的各种属性。

  • 选中文件夹后,按“command + I”键,可以查看文件夹的大小。【荐】

切换输入法

“control + space”

打开 spotlight 搜索框

spotlight 是系统自带的软件,搜索功能不是很强大。我们一般都会用第三方的 Alfred 软件。

编辑相关

Cmd+C、Cmd+V、Cmd+X、Cmd+A、Cmd+Z。

翻页和光标

  • “control + ↑”:将光标定位到文章的最开头(翻页到文档的最上方)

  • “control + ↓”:将光标定位到文章的最末尾(翻页到文档的最下方)

  • “control + ←”:将光标定位到当前行的最左侧

  • “control + →”:将光标定位到当前行的最右侧

“command + shift + Y”:将文字快速保存到便笺

选中你想要的内容(例如文字、链接等),然后按下 command + shift + Y”,那么你选中的内容就会快速保存到系统自带的“便笺”软件中。

如果你想临时性的保存一段内容,这个操作很实用。

程序相关

  • “command + Q”:快速退出程序

  • “command + tab”:切换程序

  • “command + H”:隐藏当前应用程序。这是一个有趣的快捷键。

  • “command + ,”:打开当前应用程序的“偏好设置”。

窗口相关

  • “command + N”:新建一个当前应用程序的窗口

  • “command + `”:在当前应用程序的不同窗口之间切换【很实用】

我们知道,“command + tab”是在不同的软件之间切换。但你不知道的是,“command + `”是在同一个软件的不同窗口之间切换。

  • “command + M”:将当前窗口最小化

  • “command + W”:关闭当前窗口

浏览器相关

  • “command + T”:浏览器中,新建一个标签

  • “command + W”:关闭当前标签

  • “command + R”:强制刷新。
  • “command + L”:定位到地址栏。【重要】

截图相关

  • “command + shift + 3”:截全屏(对整个屏幕截图)。

声音相关

选中文字后,按住“ctrl + esc”键,会将文字进行朗读。(我发现,在触控条版的 mac 上,并没有生效)

Dock 栏相关

  • “option + command + D”:隐藏 dock 栏

强制推出

强制退出的快捷键非常重要

  • “option + command + esc”:打开强制退出的窗口

option 相关

强烈推荐

  • “option + command + H”:隐藏除当前应用程序之外的其他应用程序

  • 在文本中,按住“option”键,配合鼠标的选中,可以进行块状文字选取。

  • “option + command + W”:快速关闭当前应用程序的所有窗口。【很实用】

比如说,你一次性打开了很多文件的详情,然后就可以通过此快捷键,将这些窗口一次性关闭。

  • “option + command + I”:查看多个文件的总的属性。
  • 打开 launchpad,按住“option”键,可以快速卸载应用程序。

  • 在 dock 栏,右键点击软件图标,同时按住“option”键,就可以强制退出该软件。【重要】

  • 在 Safari 浏览器中,按住“option + command + Q”退出 Safari。等下次进入 Safari 的时候,上次退出时的网址会自动被打开。【实用】

推荐一个软件:CheatSheet

打开 CheatSheet 后,长按 command 键,会弹出当前应用程序的所有快捷键。我们还可以对这些快捷键进行保存。

📚 学习资源

🚪 传送

| 回首頁 |

大数据简介

简介

什么是大数据

大数据是指超出传统数据库工具收集、存储、管理和分析能力的数据集。与此同时,及时采集、存储、聚合、管理数据,以及对数据深度分析的新技术和新能力,正在快速增长,就像预测计算芯片增长速度的摩尔定律一样。

  • Volume - 数据规模巨大
  • Velocity - 生成和处理速度极快
  • Variety - 数据规模巨大
  • Value - 生成和处理速度极快

应用场景

基于大数据的数据仓库

基于大数据的实时流处理

Hadoop 编年史

时间 事件
2003.01 Google 发表了 Google File System 论文
2004.01 Google 发表了 MapReduce 论文
2006.02 Apache Hadoop 项目正式启动,并支持 MapReduce 和 HDFS 独立发展
2006.11 Google 发表了 Bigtable 论文
2008.01 Hadoop 成为 Apache 顶级项目
2009.03 Cloudera 推出世界上首个 Hadoop 发行版——CDH,并完全开放源码
2012.03 HDFS NameNode HA 加入 Hadoop 主版本
2014.02 Spark 代替 MapReduce 成为 Hadoop 的缺省计算引擎,并成为 Apache 顶级项目

技术体系

HDFS

概念

  • Hadoop 分布式文件系统(Hadoop Distributed File System)
  • 在开源大数据技术体系中,地位无可替代

特点

  • 高容错:数据多副本,副本丢失后自动恢复
  • 高可用:NameNode HA,安全模式
  • 高扩展:10K 节点规模
  • 简单一致性模型:一次写入多次读取,支持追加,不允许修改
  • 流式数据访问:批量读而非随机读,关注吞吐量而非时间
  • 大规模数据集:典型文件大小 GB~TB 级,百万以上文件数量, PB 以上数据规模
  • 构建成本低且安全可靠:运行在大量的廉价商用机器上,硬件错误是常态,提供容错机制

MapReduce

概念

  • 面向批处理的分布式计算框架
  • 编程模型:将 MapReduce 程序分为 Map、Reduce 两个阶段

核心思想

  • 分而治之,分布式计算
  • 移动计算,而非移动数据

特点

  • 高容错:任务失败,自动调度到其他节点重新执行
  • 高扩展:计算能力随着节点数增加,近似线性递增
  • 适用于海量数据的离线批处理
  • 降低了分布式编程的门槛

Spark

高性能分布式通用计算引擎

  • Spark Core - 基础计算框架(批处理、交互式分析)
  • Spark SQL - SQL 引擎(海量结构化数据的高性能查询)
  • Spark Streaming - 实时流处理(微批)
  • Spark MLlib - 机器学习
  • Spark GraphX - 图计算

采用 Scala 语言开发

特点

  • 计算高效 - 内存计算、Cache 缓存机制、DAG 引擎、多线程池模型
  • 通用易用 - 适用于批处理、交互式计算、流处理、机器学习、图计算等多种场景
  • 运行模式多样 - Local、Standalone、YARN/Mesos

YARN

概念

  • Yet Another Resource Negotiator,另一种资源管理器
  • 为了解决 Hadoop 1.x 中 MapReduce 的先天缺陷
  • 分布式通用资源管理系统
  • 负责集群资源的统一管理
  • 从 Hadoop 2.x 开始,YARN 成为 Hadoop 的核心组件

特点

  • 专注于资源管理和作业调度
  • 通用 - 适用各种计算框架,如 - MapReduce、Spark
  • 高可用 - ResourceManager 高可用、HDFS 高可用
  • 高扩展

Hive

概念

  • Hadoop 数据仓库 - 企业决策支持
  • SQL 引擎 - 对海量结构化数据进行高性能的 SQL 查询
  • 采用 HDFS 或 HBase 为数据存储
  • 采用 MapReduce 或 Spark 为计算框架

特点

  • 提供类 SQL 查询语言
  • 支持命令行或 JDBC/ODBC
  • 提供灵活的扩展性
  • 提供复杂数据类型、扩展函数、脚本等

HBase

概念

  • Hadoop Database
  • Google BigTable 的开源实现
  • 分布式 NoSQL 数据库
  • 列式存储 - 主要用于半结构化、非结构化数据
  • 采用 HDFS 为文件存储系统

特点

  • 高性能 - 支持高并发写入和查询
  • 高可用 - HDFS 高可用、Region 高可用
  • 高扩展 - 数据自动切分和分布,可动态扩容,无需停机
  • 海量存储 - 单表可容纳数十亿行,上百万列

ElasticSearch

  • 开源的分布式全文检索引擎
  • 基于 Lucene 实现全文数据的快速存储、搜索和分析
  • 处理大规模数据 - PB 级以上
  • 具有较强的扩展性,集群规模可达上百台
  • 首选的分布式搜索引擎

术语

数据仓库(Data Warehouse) - 数据仓库,是为企业所有级别的决策制定过程,提供所有类型数据支持的战略集合。它是单个数据存储,出于分析性报告和决策支持目的而创建。 为需要业务智能的企业,提供指导业务流程改进、监视时间、成本、质量以及控制。

资源